/*
 *  Extended Audio Converter for SDL (Simple DirectMedia Layer)
 *  Copyright (C) 2002  Frank Ranostaj
 *                      Institute of Applied Physik
 *                      Johann Wolfgang Goethe-Universität
 *                      Frankfurt am Main, Germany
 *
 *   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
 *
 *  Frank Ranostaj
 *  ranostaj@stud.uni-frankfurt.de
 *
 * (This code blatantly abducted for SDL_sound. Thanks, Frank! --ryan.)
 */

#if HAVE_CONFIG_H
#  include <config.h>
#endif

#if SOUND_USE_ALTCVT

#include "alt_audio_convert.h"
#include <math.h>

/* just to make sure this is defined... */

#ifndef min
#define min(x, y) ( ((x) < (y)) ? (x) : (y) )
#endif

#ifndef max
#define max(x, y) ( ((x) > (y)) ? (x) : (y) )
#endif

#ifndef abs
#define abs(x) ( ((x) > (0)) ? (x) : -(x) )
#endif


/* some macros for "parsing" format */

#define IS_8BIT(x)    ((x).format & 0x0008)
#define IS_16BIT(x)   ((x).format & 0x0010)
#define IS_FLOAT(x)   ((x).format & 0x0020)
#define IS_SIGNED(x)  ((x).format & 0x8000)
#define IS_SYSENDIAN(x) ((~AUDIO_U16SYS ^ (x).format) & 0x1000)
#define SDL_MSB_POSITION_IN_SHORT ((0x1000 & AUDIO_U16SYS)>>12)


/*-------------------------------------------------------------------------*/
/* the purpose of the RateConverterBuffer is to provide a continous storage
   for head and tail of the (sample)-buffer. This allows a simple and
   perfomant implemantation of the sample rate converters. Depending of the
   operation mode, two layouts for the RateConverterBuffer.inbuffer are
   possible:

   in the Loop Mode:
   ... T-4 T-3 T-2 T-1 H+0 H+1 H+2 H+3 H+4 ...
                       |
                       linp, finp

   in the Single Mode (non Loop):
   ... T-4 T-3 T-2 T-1 0   0   0 ... 0   0   0   H+0 H+1 H+2 H+3 H+4 ...
                       |                          |
                       linp                       finp

   The RateConverterBuffer allows an accurate attack and decay of the
   filters in the rate Converters.

   The pointer finp are actually shifted against the depicted position so
   that on the first invocation of the rate converter the input of the
   filter is nearly complete in the zero region, only one input value is
   used. After the calculation of the first output value, the pointer are
   incremented or decremented depending on down or up conversion and the
   first two input value are taken into account. This procedure repeats
   until the filter has processed all zeroes. The distance of the pointer
   movement is stored in flength, always positive.

   Further a pointer cinp to the sample buffer itself is stored. The pointer
   to the sample buffer is shifted too, so that on the first use of this
   pointer the filter is complete in the sample buffer. The pointer moves
   over the sample buffer until it reaches the other end. The distance of
   the movement is stored in clength.

   Finally the decay of the filter is done by linp and llength like finp,
   flength, but in reverse order.

   buffer denotes the start or the end of the output buffer, depending
   on direction of the rate conversion.

   All pointer and length referring the buffer as Sint16. All length
   are refering to the input buffer */

typedef struct
{
    Sint16 inbuffer[24*_fsize];
    Sint16 *finp, *cinp, *linp;
    int flength, clength, llength;
    Sint16 *buffer;
    VarFilter *filter;
} RateConverterBuffer;

typedef struct
{
    Sint16 carry;
    Sint16 pos;
} RateAux;


/* Mono (1 channel ) */
#define Suffix(x) x##1
#include "filter_templates.h"
#undef Suffix

/* Stereo (2 channel ) */
#define Suffix(x) x##2
#include "filter_templates.h"
#undef Suffix


/*-------------------------------------------------------------------------*/
int Sound_estimateBufferSize( Sound_AudioCVT *Data, int size )
{
    size *= Data->len_mult;
    size += Data->len_add;
    return ( size + 3 ) & -4; /* force Size in multipels of 4 Byte */
}

/*-------------------------------------------------------------------------*/
int Sound_AltConvertAudio( Sound_AudioCVT *Data,
    Uint8* buffer, int length, int mode )
{
    AdapterC Temp;
    int i;

    /* Make sure there's a converter */
    if( Data == NULL ) {
        SDL_SetError("No converter given");
        return(-1);
    }

    /* Make sure there's data to convert */
    if( buffer == NULL ) {
        SDL_SetError("No buffer allocated for conversion");
        return(-1);
    }

    if( length < 0 ) {
        SDL_SetError("Lenght < 0");
        return(-1);
    }

    /* Set up the conversion and go! */
    Temp.buffer = buffer;
    Temp.mode = mode;
    Temp.filter = &Data->filter;

    for( i = 0; Data->adapter[i] != NULL; i++ )
	length = (*Data->adapter[i])( Temp, length);

    return length;
}

int Sound_ConvertAudio( Sound_AudioCVT *Data )
{
    int length;
    /* !!! FIXME: Try the looping stuff under certain circumstances? --ryan. */
    length = Sound_AltConvertAudio( Data, Data->buf, Data->len, 0 );
    Data->len_cvt = length;
    return length;
}

/*-------------------------------------------------------------------------*/
static int expand8BitTo16BitSys( AdapterC Data, int length )
{
    int i;
    Uint8* inp = Data.buffer - 1;
    Uint16* buffer = (Uint16*)Data.buffer - 1;
    for( i = length + 1; --i; )
         buffer[i] = inp[i]<<8;
    return 2*length;
}

static int expand8BitTo16BitWrong( AdapterC Data, int length )
{
    int i;
    Uint8* inp = Data.buffer - 1;
    Uint16* buffer = (Uint16*)Data.buffer - 1;
    for( i = length + 1; --i; )
         buffer[i] = inp[i];
    return 2*length;
}

/*-------------------------------------------------------------------------*/
static int expand16BitToFloat( AdapterC Data, int length )
{
    int i;
    Sint16* inp = (Sint16*)Data.buffer - 1;
    float* buffer = (float*)Data.buffer - 1;
    for( i = length>>1 + 1; --i; )
         buffer[i] = inp[i]*(1./32767);
    return 2*length;
}

/*-------------------------------------------------------------------------*/
static int swapBytes( AdapterC Data, int length )
{
   /*
    * !!! FIXME !!!
    *
    *
    * Use the faster SDL-Macros to swap
    * - Frank
    */

    int i;
    Uint16 a,b;
    Uint16* buffer = (Uint16*) Data.buffer - 1;
    for( i = length>>1 + 1; --i; )
    {
         a = b = buffer[i];
         buffer[i] = ( a << 8 ) | ( b >> 8 );
    }
    return length;
}

/*-------------------------------------------------------------------------*/
static int cutFloatTo16Bit( AdapterC Data, int length )
{
    int i;
    float* inp = (float*) Data.buffer;
    Sint16* buffer = (Sint16*) Data.buffer;
    length>>=2;
    for( i = 0; i < length; i++ )
    {
         if( inp[i] > 1. )
             buffer[i] = 32767;
         else if( inp[i] < -1. )
             buffer[i] = -32768;
         else
             buffer[i] = 32767 * inp[i];
    }
    return 2*length;
}

/*-------------------------------------------------------------------------*/
static int cut16BitTo8Bit( AdapterC Data, int length, int off )
{
    int i;
    Uint8* inp = Data.buffer + off;
    Uint8* buffer = Data.buffer;
    length >>= 1;
    for( i = 0; i < length; i++ )
         buffer[i] = inp[2*i];
    return length;
}

static int cut16BitSysTo8Bit( AdapterC Data, int length )
{
    return cut16BitTo8Bit( Data, length, SDL_MSB_POSITION_IN_SHORT );
}

static int cut16BitWrongTo8Bit( AdapterC Data, int length )
{
    return cut16BitTo8Bit( Data, length, 1-SDL_MSB_POSITION_IN_SHORT );
}

/*-------------------------------------------------------------------------*/
/* poor mans mmx :-)                                                       */
static int changeSigned( AdapterC Data, int length, Uint32 XOR )
{
    int i;
    Uint32* buffer = (Uint32*) Data.buffer - 1;
    for( i = ( length + 7 ) >> 2; --i;  )
         buffer[i] ^= XOR;
    return length;
}

static int changeSigned16BitSys( AdapterC Data, int length )
{
    return changeSigned( Data, length, 0x80008000 );
}

static int changeSigned16BitWrong( AdapterC Data, int length )
{
    return changeSigned( Data, length, 0x00800080 );
}

static int changeSigned8Bit( AdapterC Data, int length )
{
    return changeSigned( Data, length, 0x80808080 );
}

/*-------------------------------------------------------------------------*/
static int convertStereoToMonoS16Bit( AdapterC Data, int length )
{
    int i;
    Sint16* buffer = (Sint16*) Data.buffer;
    Sint16* src = (Sint16*) Data.buffer;
    length >>= 2;
    for( i = 0; i < length;  i++, src+=2 )
         buffer[i] = ((int) src[0] + src[1] ) >> 1;
    return 2*length;
}

static int convertStereoToMonoU16Bit( AdapterC Data, int length )
{
    int i;
    Uint16* buffer = (Uint16*) Data.buffer;
    Uint16* src = (Uint16*) Data.buffer;
    length >>= 2;
    for( i = 0; i < length;  i++, src+=2 )
         buffer[i] = ((int) src[0] + src[1] ) >> 1;
    return 2*length;
}

static int convertStereoToMonoS8Bit( AdapterC Data, int length )
{
    int i;
    Sint8* buffer = (Sint8*) Data.buffer;
    Sint8* src = (Sint8*) Data.buffer;
    length >>= 1;
    for( i = 0; i < length;  i++, src+=2 )
         buffer[i] = ((int) src[0] + src[1] ) >> 1;
    return length;
}

static int convertStereoToMonoU8Bit( AdapterC Data, int length )
{
    int i;
    Uint8* buffer = (Uint8*) Data.buffer;
    Uint8* src = (Uint8*) Data.buffer;
    length >>= 1;
    for( i = 0; i < length;  i++, src+=2 )
         buffer[i] = ((int) src[0] + src[1] ) >> 1;
    return length;
}

/*-------------------------------------------------------------------------*/
static int convertMonoToStereo16Bit( AdapterC Data, int length )
{
    int i;
    Uint16* buffer;
    Uint16* dst;

    length >>=1;
    buffer = (Uint16*)Data.buffer - 1;
    dst = (Uint16*)Data.buffer + 2*length - 2;
    for( i = length + 1; --i; dst-=2 )
         dst[0] = dst[1] = buffer[i];
    return 4*length;
}

static int convertMonoToStereo8Bit( AdapterC Data, int length )
{
    int i;
    Uint8* buffer = Data.buffer - 1;
    Uint8* dst = Data.buffer + 2*length - 2;
    for( i = length + 1; --i; dst-=2 )
         dst[0] = dst[1] = buffer[i];
    return 2*length;
}

/*-------------------------------------------------------------------------*/
static int minus5dB( AdapterC Data, int length )
{
    int i;
    Sint16* buffer = (Sint16*) Data.buffer;
    for(i = length>>1 + 1; --i;  )
        buffer[i] = (38084 * (int)buffer[i]) >> 16;
    return length;
}

/*-------------------------------------------------------------------------*/
const Fraction Half = {1, 2};
const Fraction Double = {2, 1};
const Fraction One = {1, 1};


static void initStraigthBuffer( RateConverterBuffer *rcb,
                                int length, Fraction r )
{
    int i, size, minsize;
    size = 8 * _fsize;
    minsize = min( size, length );

    for( i = 0; i < minsize; i++ )
    {
        rcb->inbuffer[i] = rcb->buffer[length-size+i];
        rcb->inbuffer[i+size] = 0;
        rcb->inbuffer[i+2*size] = rcb->buffer[i];
    }
    for( ; i < size; i++ )
    {
        rcb->inbuffer[i] = 0;
        rcb->inbuffer[i+size] = 0;
        rcb->inbuffer[i+2*size] = 0;
    }

    length = max( length, size );
    rcb->flength = rcb->llength = size;
    rcb->clength = length - size;

    if( r.numerator < r.denominator )
    {
        rcb->finp = rcb->inbuffer + 5*size/2;
        rcb->cinp = rcb->buffer + length - size/2;
        rcb->linp = rcb->inbuffer + 3*size/2;
        rcb->buffer += ( 1 + r.denominator * ( length + size )
                       / r.numerator ) & -2;
    }
    else
    {
        rcb->finp = rcb->inbuffer + size/2;
        rcb->cinp = rcb->buffer + size/2;
        rcb->linp = rcb->inbuffer + 3*size/2;
    }
}

static void initLoopBuffer( RateConverterBuffer *rcb,
                            int length, Fraction r )
{
    /* !!!FIXME: modulo length, take scale into account,
                 check against the Straight part -frank */
    int i, size;
    size = 8 * _fsize;
    for( i = 0; i < size; i++ )
    {
        rcb->inbuffer[i] = rcb->buffer[length-size+i];
        rcb->inbuffer[i+size] = rcb->buffer[i];
    }
    rcb->finp = rcb->linp = rcb->inbuffer + size;
    if( size < 0 )
        rcb->buffer += r.numerator * ( length + 2 * size )
                       / r.denominator;
}

static void initRateConverterBuffer( RateConverterBuffer *rcb,
    AdapterC* Data, int length, Fraction ratio )
{
    length >>= 1;
    rcb->buffer = (Sint16*)( Data->buffer );
    rcb->filter = Data->filter;

    if( Data->mode & SDL_SOUND_Loop )
        initLoopBuffer( rcb, length, ratio );
    else
        initStraigthBuffer( rcb, length, ratio );

    fprintf( stderr, " finp: %8x length: %8x\n", rcb->finp, rcb->flength );
    fprintf( stderr, " cinp: %8x length: %8x\n", rcb->cinp, rcb->clength );
    fprintf( stderr, " linp: %8x length: %8x\n", rcb->linp, rcb->llength );
}

static void nextRateConverterBuffer( RateConverterBuffer *rcb )
{
    rcb->buffer++;
    rcb->finp++;
    rcb->cinp++;
    rcb->linp++;
}

typedef Sint16* (*RateConverter)( Sint16*, Sint16*, int,
                                  VarFilter*, RateAux* );

static Sint16* doRateConversion( RateConverterBuffer* rcb, RateConverter rc )
{
    RateAux aux = {0,0};
    Sint16 *outp = rcb->buffer;
    VarFilter* filter = rcb->filter;

    outp = (*rc)( outp, rcb->finp, rcb->flength, filter, &aux );
    fprintf( stderr, " outp: %8x aux.carry: %8x\n", outp, aux.carry );
    outp = (*rc)( outp, rcb->cinp, rcb->clength, filter, &aux );
    fprintf( stderr, " outp: %8x aux.carry: %8x\n", outp, aux.carry );
    outp = (*rc)( outp, rcb->linp, rcb->llength, filter, &aux );
    fprintf( stderr, " outp: %8x aux.carry: %8x\n", outp, aux.carry );
    return outp;
}


/*-------------------------------------------------------------------------*/
static void clearSint16Buffer( Sint8* buffer, Sint16*r )
{
    while( r >= (Sint16*)buffer ) *r-- = 0;
}

/*-------------------------------------------------------------------------*/
static int doubleRateMono( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    initRateConverterBuffer( &rcb, &Data, length, Half );
    r = 1 + doRateConversion( &rcb, doubleRate1 );
    clearSint16Buffer( Data.buffer, r );
    return 2 * ( rcb.buffer - (Sint16*)Data.buffer + 2 );
}

static int doubleRateStereo( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    fprintf( stderr, "\n Buffer: %8x length: %8x\n", Data.buffer, length );
    initRateConverterBuffer( &rcb, &Data, length, Half );
    doRateConversion( &rcb, doubleRate2 );
    nextRateConverterBuffer( &rcb );
    r = 2 + doRateConversion( &rcb, doubleRate2 );
    clearSint16Buffer( Data.buffer, r );
    return 2 * ( rcb.buffer - (Sint16*)Data.buffer + 3 );
}

/*-------------------------------------------------------------------------*/
static int halfRateMono( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    initRateConverterBuffer( &rcb, &Data, length, Double );
    r = doRateConversion( &rcb, halfRate1 );
    return 2 * ( r - (Sint16*)Data.buffer );
}

static int halfRateStereo( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    initRateConverterBuffer( &rcb, &Data, length, Double );
    doRateConversion( &rcb, halfRate2 );
    nextRateConverterBuffer( &rcb );
    r = doRateConversion( &rcb, halfRate2 );
    return 2 * ( r - (Sint16*)Data.buffer );
}

/*-------------------------------------------------------------------------*/
static int increaseRateMono( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    initRateConverterBuffer( &rcb, &Data, length, Data.filter->ratio );
    r = doRateConversion( &rcb, increaseRate1 );
    clearSint16Buffer( Data.buffer, r );
    return 2 * ( rcb.buffer - (Sint16*)Data.buffer + 1 );
}

static int increaseRateStereo( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    fprintf( stderr, "\n Buffer: %8x length: %8x\n", Data.buffer, length );
    initRateConverterBuffer( &rcb, &Data, length, Data.filter->ratio );
    doRateConversion( &rcb, increaseRate2 );
    nextRateConverterBuffer( &rcb );
    r = doRateConversion( &rcb, increaseRate2 );
    clearSint16Buffer( Data.buffer, r );
    return 2 * ( rcb.buffer - (Sint16*)Data.buffer + 1 );
}

/*-------------------------------------------------------------------------*/
static int decreaseRateMono( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    initRateConverterBuffer( &rcb, &Data, length, Data.filter->ratio );
    r = doRateConversion( &rcb, decreaseRate1 );
    return 2 * ( r - (Sint16*)Data.buffer );
}

static int decreaseRateStereo( AdapterC Data, int length )
{
    Sint16* r;
    RateConverterBuffer rcb;
    initRateConverterBuffer( &rcb, &Data, length, Data.filter->ratio );
    doRateConversion( &rcb, decreaseRate2 );
    nextRateConverterBuffer( &rcb );
    r = doRateConversion( &rcb, decreaseRate2 );
    return 2 * ( r - (Sint16*)Data.buffer );
}

/*-------------------------------------------------------------------------*/
/* gives a maximal error of 3% and typical less than 0.2% */
static Fraction findFraction( float Value )
{
    const Sint8 frac[95]={
         2,                                                  -1, /*  /1 */
      1,    3,                                               -1, /*  /2 */
         2,    4, 5,                                         -1, /*  /3 */
            3,    5,    7,                                   -1, /*  /4 */
            3, 4,    6, 7, 8, 9,                             -1, /*  /5 */
                  5,    7,           11,                     -1, /*  /6 */
               4, 5, 6,    8, 9, 10, 11, 12, 13,             -1, /*  /7 */
                  5,    7,    9,     11,     13,     15,     -1, /*  /8 */
                  5,    7, 8,    10, 11,     13, 14,     16, -1, /*  /9 */
                        7,    9,     11,     13,             -1, /* /10 */
                     6, 7, 8, 9, 10,     12, 13, 14, 15, 16, -1, /* /11 */
                        7,           11,     13,             -1, /* /12 */
                        7, 8, 9, 10, 11, 12,     14, 15, 16, -1, /* /13 */
                              9,     11,     13,     15,     -1, /* /14 */
                           8,        11,     13, 14,     16, -1, /* /15 */
                              9,     11,     13,     15       }; /* /16 */


    Fraction Result = {0,0};
    int i,num,den=1;

    float RelErr, BestErr = 0;
    if( Value < 31/64. || Value > 64/31. ) return Result;

    for( i = 0; i < SDL_TABLESIZE(frac); i++ )
    {
         num = frac[i];
         if( num < 0 ) den++;
         RelErr = Value * num / den;
         RelErr = min( RelErr, 1/RelErr );
         if( RelErr > BestErr )
         {
             BestErr = RelErr;
             Result.denominator = den;
             Result.numerator = num;
         }
    }
    return Result;
}

/*-------------------------------------------------------------------------*/
static float sinc( float x )
{
    if( x > -1e-24 && x < 1e-24 ) return 1.;
    else return sin(x)/x;
}

static float calculateVarFilter( Sint16* dst,
                                 float Ratio, float phase, float scale )
{
    const Uint16 KaiserWindow7[]= {
        22930, 16292, 14648, 14288, 14470, 14945, 15608, 16404,
        17304, 18289, 19347, 20467, 21644, 22872, 24145, 25460,
        26812, 28198, 29612, 31052, 32513, 33991, 35482, 36983,
        38487, 39993, 41494, 42986, 44466, 45928, 47368, 48782,
        50165, 51513, 52821, 54086, 55302, 56466, 57575, 58624,
        59610, 60529, 61379, 62156, 62858, 63483, 64027, 64490,
        64870, 65165, 65375, 65498, 65535, 65484, 65347, 65124,
        64815, 64422, 63946, 63389, 62753, 62039, 61251, 60391 };
    int i;
    float w;
    const float fg = -.018 + .5 * Ratio;
    const float omega = 2 * M_PI * fg;
    fprintf( stderr, "    phase: %6g \n", phase );
    phase += 63;
    for( i = 0; i < 64; i++)
    {
        w = scale * ( KaiserWindow7[i] * ( i + 1 ));
        dst[i] = w * sinc( omega * (i-phase) );
        dst[127-i] = w * sinc( omega * (127-i-phase) );
    }
    fprintf( stderr, "    center: %6d %6d \n", dst[63], dst[64] );
    return fg;
}

static Fraction setupVarFilter( Sound_AudioCVT *Data, float Ratio )
{
    int pos,n,d, incr, phase = 0;
    float Scale, rd, fg;
    Fraction IRatio;
    VarFilter* filter = &Data->filter;

    IRatio = findFraction( Ratio );
//    Scale = Ratio < 1. ? 0.0364733 : 0.0211952;
    Scale = 0.0084778;
    Ratio = min( Ratio, 0.97 );

    filter->ratio = IRatio;
    n = IRatio.numerator;
    d = IRatio.denominator;
    rd = 1. / d;

    fprintf( stderr, "Filter:\n" );

    for( pos = 0; pos < d; pos++ )
    {
        fg = calculateVarFilter( filter->c[pos], Ratio, phase*rd, Scale );
        phase += n;
        filter->incr[pos] = phase / d;
        phase %= d;
    }
    fprintf( stderr, "    fg:  %6g\n\n", fg );
/* !!!FIXME: get rid of the inversion -Frank*/
    IRatio.numerator = d;
    IRatio.denominator = n;
    return IRatio;
}
/*-------------------------------------------------------------------------*/
static void initAudioCVT( Sound_AudioCVT *Data )
{
    Data->len_ratio = 1.;
    Data->len_mult = 1;
    Data->add = 0;
    Data->len_add = 0;
    Data->filter_index = 0;
}

static void adjustSize( Sound_AudioCVT *Data, int add, Fraction f )
{
    double ratio = f.numerator / (double) f.denominator;
    Data->len_ratio *= ratio;
    Data->len_mult = max( Data->len_mult, ceil(Data->len_ratio) );
    Data->add = ratio * (Data->add + add);
    Data->len_add = max( Data->len_add, ceil(Data->add) );
}

static Adapter* addAdapter( Sound_AudioCVT *Data, Adapter a )
{
    Data->adapter[Data->filter_index] = a;
    return &Data->adapter[Data->filter_index++];
}

static void addHAdapter( Sound_AudioCVT *Data, Adapter a )
{
    adjustSize( Data, 0, Half );
    addAdapter( Data, a );
}

static void addDAdapter( Sound_AudioCVT *Data, Adapter a )
{
    adjustSize( Data, 0, Double );
    addAdapter( Data, a );
}


/*-------------------------------------------------------------------------*/
const Adapter doubleRate[2] = { doubleRateMono, doubleRateStereo };
const Adapter halfRate[2] = { halfRateMono, halfRateStereo };
const Adapter increaseRate[2] = { increaseRateMono, increaseRateStereo };
const Adapter decreaseRate[2] = { decreaseRateMono, decreaseRateStereo };

static int createRateConverter( Sound_AudioCVT *Data,
                                int SrcRate, int DestRate, int channel )
{
    const int c = channel - 1;
    const int size = 16 * channel * _fsize;
    Adapter* AdapterPos;
    float Ratio = DestRate;
    Fraction f;

    if( SrcRate < 1 || SrcRate > 1<<18 ||
        DestRate < 1 || DestRate > 1<<18 ) return -1;
    Ratio /= SrcRate;

    AdapterPos = addAdapter( Data, minus5dB );

    while( Ratio > 64./31.)
    {
        Ratio /= 2.;
        addAdapter( Data, doubleRate[c] );
        adjustSize( Data, size, Double );
    }

    while( Ratio < 31./64. )
    {
        Ratio *= 2;
        addAdapter( Data, halfRate[c] );
        adjustSize( Data, size, Half );
    }

    if( Ratio > 1. )
    {
        *AdapterPos = increaseRate[c];
        f = setupVarFilter( Data, Ratio );
        adjustSize( Data, size, f );
    }
    else
    {
        f = setupVarFilter( Data, Ratio );
        addAdapter( Data, decreaseRate[c]);
        adjustSize( Data, size, f );
    }

    return 0;
}

/*-------------------------------------------------------------------------*/
static void createFormatConverter16Bit(Sound_AudioCVT *Data,
    SDL_AudioSpec src, SDL_AudioSpec dst )
{
    if( src.channels == 2 && dst.channels == 1 )
    {
        if( !IS_SYSENDIAN(src) )
            addAdapter( Data, swapBytes );

        if( IS_SIGNED(src) )
            addHAdapter( Data, convertStereoToMonoS16Bit );
        else
            addHAdapter( Data, convertStereoToMonoU16Bit );

        if( !IS_SYSENDIAN(dst) )
            addAdapter( Data, swapBytes );
    }
    else if( IS_SYSENDIAN(src) != IS_SYSENDIAN(dst) )
        addAdapter( Data, swapBytes );

    if( IS_SIGNED(src) != IS_SIGNED(dst) )
    {
        if( IS_SYSENDIAN(dst) )
            addAdapter( Data, changeSigned16BitSys );
        else
            addAdapter( Data, changeSigned16BitWrong );
    }

    if( src.channels == 1 && dst.channels == 2 )
        addDAdapter( Data, convertMonoToStereo16Bit );
}

/*-------------------------------------------------------------------------*/
static void createFormatConverter8Bit(Sound_AudioCVT *Data,
    SDL_AudioSpec src, SDL_AudioSpec dst )
{
    if( IS_16BIT(src) )
    {
        if( IS_SYSENDIAN(src) )
            addHAdapter( Data, cut16BitSysTo8Bit );
        else
            addHAdapter( Data, cut16BitWrongTo8Bit );
    }

    if( src.channels == 2 && dst.channels == 1 )
    {
        if( IS_SIGNED(src) )
            addHAdapter( Data, convertStereoToMonoS8Bit );
        else
            addHAdapter( Data, convertStereoToMonoU8Bit );
    }

    if( IS_SIGNED(src) != IS_SIGNED(dst) )
        addDAdapter( Data, changeSigned8Bit );

    if( src.channels == 1 && dst.channels == 2  )
        addDAdapter( Data, convertMonoToStereo8Bit );

    if( !IS_8BIT(dst) )
    {
        if( IS_SYSENDIAN(dst) )
            addDAdapter( Data, expand8BitTo16BitSys );
        else
            addDAdapter( Data, expand8BitTo16BitWrong );
    }
}

/*-------------------------------------------------------------------------*/
static void createFormatConverter(Sound_AudioCVT *Data,
    SDL_AudioSpec src, SDL_AudioSpec dst )
{
    if( IS_FLOAT(src) )
        addHAdapter( Data, cutFloatTo16Bit );

    if( IS_8BIT(src) || IS_8BIT(dst) )
         createFormatConverter8Bit( Data, src, dst);
    else
         createFormatConverter16Bit( Data, src, dst);

    if( IS_FLOAT(dst) )
        addDAdapter( Data, expand16BitToFloat );
}

/*-------------------------------------------------------------------------*/
int Sound_AltBuildAudioCVT( Sound_AudioCVT *Data,
                   SDL_AudioSpec src, SDL_AudioSpec dst )
{
    SDL_AudioSpec im;

    if( Data == NULL ) return -1;

    initAudioCVT( Data );
    Data->filter.ratio.denominator = 0;
    Data->filter.mask = dst.size - 1;

    /* Check channels */
    if( src.channels < 1 || src.channels > 2 ||
        dst.channels < 1 || dst.channels > 2 ) goto error_exit;

    if( src.freq != dst.freq )
    {
    /* Convert to intermidiate format: signed 16Bit System-Endian */
        im.format = AUDIO_S16SYS;
        im.channels = min( src.channels, dst.channels );
        createFormatConverter( Data, src, im );

    /* Do rate conversion */
        if( createRateConverter( Data, src.freq, dst.freq, im.channels ) )
            goto error_exit;

       src = im;
    }

    /* Convert to final format */
    createFormatConverter( Data, src, dst );

    /* Finalize adapter list */
    addAdapter( Data, NULL );
/* !!! FIXME: Is it okay to assign NULL to a function pointer?
              Borland says no. -frank */
    return 0;

error_exit:
/* !!! FIXME: Is it okay to assign NULL to a function pointer?
              Borland says no. -frank    */
    Data->adapter[0] = NULL;
    return -1;
}

/*-------------------------------------------------------------------------*/
static char *fmt_to_str(Uint16 fmt)
{
    switch (fmt)
    {
        case AUDIO_U8:     return "    U8";
        case AUDIO_S8:     return "    S8";
        case AUDIO_U16MSB: return "U16MSB";
        case AUDIO_S16MSB: return "S16MSB";
        case AUDIO_U16LSB: return "U16LSB";
        case AUDIO_S16LSB: return "S16LSB";
    }
    return "??????";
}

#define AdapterDesc(x) { x, #x }

static void show_AudioCVT( Sound_AudioCVT *Data )
{
    int i,j;
    const struct{ int (*adapter) ( AdapterC, int); Sint8 *name; }
    AdapterDescription[] = {
        AdapterDesc(expand8BitTo16BitSys),
        AdapterDesc(expand8BitTo16BitWrong),
        AdapterDesc(expand16BitToFloat),
        AdapterDesc(swapBytes),
        AdapterDesc(cut16BitSysTo8Bit),
        AdapterDesc(cut16BitWrongTo8Bit),
        AdapterDesc(cutFloatTo16Bit),
        AdapterDesc(changeSigned16BitSys),
        AdapterDesc(changeSigned16BitWrong),
        AdapterDesc(changeSigned8Bit),
        AdapterDesc(convertStereoToMonoS16Bit),
        AdapterDesc(convertStereoToMonoU16Bit),
        AdapterDesc(convertStereoToMonoS8Bit),
        AdapterDesc(convertStereoToMonoU8Bit),
        AdapterDesc(convertMonoToStereo16Bit),
        AdapterDesc(convertMonoToStereo8Bit),
        AdapterDesc(minus5dB),
        AdapterDesc(doubleRateMono),
        AdapterDesc(doubleRateStereo),
        AdapterDesc(halfRateMono),
        AdapterDesc(halfRateStereo),
        AdapterDesc(increaseRateMono),
        AdapterDesc(increaseRateStereo),
        AdapterDesc(decreaseRateMono),
        AdapterDesc(decreaseRateStereo),
        { NULL,    "----------NULL-----------\n" }
    };

    fprintf( stderr, "Sound_AudioCVT:\n" );
    fprintf( stderr, "    needed:      %8d\n", Data->needed );
    fprintf( stderr, "    add:         %8g\n", Data->add );
    fprintf( stderr, "    len_add:     %8d\n", Data->len_add );
    fprintf( stderr, "    len_ratio:   %8g\n", Data->len_ratio );
    fprintf( stderr, "    len_mult:    %8d\n", Data->len_mult );
    fprintf( stderr, "    filter->mask: %#7x\n", Data->filter.mask );
    fprintf( stderr, "\n" );

    fprintf( stderr, "Adapter List:    \n" );
    for( i = 0; i < 32; i++ )
    {
        for( j = 0; j < SDL_TABLESIZE(AdapterDescription); j++ )
        {
            if( Data->adapter[i] == AdapterDescription[j].adapter )
            {
                fprintf( stderr, "    %s \n", AdapterDescription[j].name );
                if( Data->adapter[i] == NULL ) goto sucess_exit;
                goto cont;
            }
        }
        fprintf( stderr, "    Error: unknown adapter\n" );

        cont:
    }
    fprintf( stderr, "    Error: NULL adapter missing\n" );
    sucess_exit:
    if( Data->filter.ratio.denominator )
    {
        fprintf( stderr, "Variable Rate Converter:\n"
                         "    numerator:   %3d\n"
                         "    denominator: %3d\n",
                         Data->filter.ratio.denominator,
                         Data->filter.ratio.numerator );

        fprintf( stderr, "    increment sequence:\n"
                         "    " );
        for( i = 0; i < Data->filter.ratio.denominator; i++ )
             fprintf( stderr, "%1d ", Data->filter.incr[i] );

        fprintf( stderr, "\n" );
    }
    else
    {
        fprintf( stderr, "No Variable Rate Converter\n" );
    }
}


int Sound_BuildAudioCVT(Sound_AudioCVT *Data,
    Uint16 src_format, Uint8 src_channels, Uint32 src_rate,
    Uint16 dst_format, Uint8 dst_channels, Uint32 dst_rate, Uint32 bufsize)
{
    SDL_AudioSpec src, dst;
    int ret;

    fprintf (stderr,
             "Sound_BuildAudioCVT():\n"
             "-----------------------------\n"
             "format:    %s ->  %s\n"
             "channels:  %6d ->  %6d\n"
             "rate:      %6d ->  %6d\n"
             "size:  don't care -> %#7x\n\n",
             fmt_to_str (src_format), fmt_to_str (dst_format),
             src_channels,            dst_channels,
             src_rate,                dst_rate );

    src.format = src_format;
    src.channels = src_channels;
    src.freq = src_rate;

    dst.format = dst_format;
    dst.channels = dst_channels;
    dst.freq = dst_rate;

    ret = Sound_AltBuildAudioCVT( Data, src, dst );
    Data->needed = 1;

    show_AudioCVT( Data );
    fprintf (stderr, "\n"
                     "return value: %d \n\n\n", ret );
    return ret;
}

#endif  /* SOUND_USE_ALTCVT */

/* end of alt_audio_convert.c ... */

