/*
 *  pa_conversions.c
 *  portaudio
 *
 *  Created by Phil Burk on Mon Mar 18 2002.
 *
 */
#include <stdio.h>

#include "portaudio.h"
#include "pa_host.h"

#define CLIP( val, min, max )  { val = ((val) < (min)) ? min : (((val) < (max)) ? (max) : (val)); }

/*************************************************************************/
static void PaConvert_Float32_Int16(
    float *sourceBuffer, int sourceStride,
    short *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
        short samp = (short) (*sourceBuffer * (32767.0f));
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int16_Clip(
    float *sourceBuffer, int sourceStride,
    short *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
        long samp = (long) (*sourceBuffer * (32767.0f));
        CLIP( samp, -0x8000, 0x7FFF );
        *targetBuffer = (short) samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int16_ClipDither(
    float *sourceBuffer, int sourceStride,
    short *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
    // use smaller scaler to prevent overflow when we add the dither
        float dither  = PaConvert_TriangularDither() * PA_DITHER_SCALE;
        float dithered = (*sourceBuffer * (32766.0f)) + dither;
        long samp = (long) dithered;
        CLIP( samp, -0x8000, 0x7FFF );
        *targetBuffer = (short) samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int16_Dither(
    float *sourceBuffer, int sourceStride,
    short *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
    // use smaller scaler to prevent overflow when we add the dither
        float dither  = PaConvert_TriangularDither() * PA_DITHER_SCALE;
        float dithered = (*sourceBuffer * (32766.0f)) + dither;
        *targetBuffer = (short) dithered;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}


/*************************************************************************/
static void PaConvert_Int16_Float32(
    short *sourceBuffer, int sourceStride,
    float *targetBuffer, int targetStride,
    int numSamples )
{
    int i;
	for( i=0; i<numSamples; i++ )
	{
        float samp = *sourceBuffer * (1.0f / 32768.0f);
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int8(
    float *sourceBuffer, int sourceStride,
    char *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
        char samp = (char) (*sourceBuffer * (127.0));
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}


/*************************************************************************/
static void PaConvert_Float32_Int8_Clip(
    float *sourceBuffer, int sourceStride,
    char *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
        long samp = (long) (*sourceBuffer * 127.0f);
        CLIP( samp, -0x80, 0x7F );
        *targetBuffer = (char) samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int8_ClipDither(
    float *sourceBuffer, int sourceStride,
    char *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
    // use smaller scaler to prevent overflow when we add the dither
        float dither  = PaConvert_TriangularDither() * PA_DITHER_SCALE;
        float dithered = (*sourceBuffer * (126.0f)) + dither;
        long samp = (long) dithered;
        CLIP( samp, -0x80, 0x7F );
        *targetBuffer = (char) samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int8_Dither(
    float *sourceBuffer, int sourceStride,
    char *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
    // use smaller scaler to prevent overflow when we add the dither
        float dither  = PaConvert_TriangularDither() * PA_DITHER_SCALE;  //FIXME
        float dithered = (*sourceBuffer * (126.0f)) + dither;
        long samp = (long) dithered;
        *targetBuffer = (char) samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Int8_Float32(
    char *sourceBuffer, int sourceStride,
    float *targetBuffer, int targetStride,
    int numSamples )
{
    int i;
	for( i=0; i<numSamples; i++ )
	{
        float samp = *sourceBuffer * (1.0f / 128.0f);
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_UInt8(
    float *sourceBuffer, int sourceStride,
    unsigned char *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
        unsigned char samp = (unsigned char)(128 + (*sourceBuffer * (127.0)));
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}
    
/*************************************************************************/
static void PaConvert_UInt8_Float32(
    unsigned char *sourceBuffer, int sourceStride,
    float *targetBuffer, int targetStride,
    int numSamples )
{
    int i;
	for( i=0; i<numSamples; i++ )
	{
        float samp = (*sourceBuffer - 128) * (1.0f / 128.0f);
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int32(
    float *sourceBuffer, int sourceStride,
    long *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
        int samp = (int) (*sourceBuffer * 0x7FFFFFFF);
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Float32_Int32_Clip(
    float *sourceBuffer, int sourceStride,
    long *targetBuffer, int targetStride,
    int numSamples )
{
	int i;
	for( i=0; i<numSamples; i++ )
	{
        int samp;
        float fs = *sourceBuffer;
        CLIP( fs, -1.0f, 0.999999f );
        samp = (int) (*sourceBuffer * 0x7FFFFFFF);
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static void PaConvert_Int32_Float32(
    long *sourceBuffer, int sourceStride,
    float *targetBuffer, int targetStride,
    int numSamples )
{
    int i;
	for( i=0; i<numSamples; i++ )
	{
        float samp = *sourceBuffer * (1.0f / 0x7FFFFFFF);
        *targetBuffer = samp;
        sourceBuffer += sourceStride;
        targetBuffer += targetStride;
    }
}

/*************************************************************************/
static PortAudioConverter *PaConvert_SelectProc( PaSampleFormat sourceFormat,
        PaSampleFormat targetFormat,  int ifClip, int ifDither )
{
    PortAudioConverter *proc = NULL;
    switch( sourceFormat )
    {
    case paUInt8:
        switch( targetFormat )
        {
        case paFloat32:
            proc = (PortAudioConverter *) PaConvert_UInt8_Float32;
            break;
        default:
            break;
        }
        break;
    case paInt8:
        switch( targetFormat )
        {
        case paFloat32:
            proc = (PortAudioConverter *) PaConvert_Int8_Float32;
            break;
        default:
            break;
        }
        break;
    case paInt16:
        switch( targetFormat )
        {
        case paFloat32:
            proc = (PortAudioConverter *) PaConvert_Int16_Float32;
            break;
        default:
            break;
        }
        break;

    case paInt32:
        switch( targetFormat )
        {
        case paFloat32:
            proc = (PortAudioConverter *) PaConvert_Int32_Float32;
            break;
        default:
            break;
        }
        break;
        
    case paFloat32:
        switch( targetFormat )
        {
        case paUInt8:
            proc = (PortAudioConverter *) PaConvert_Float32_UInt8;
            break;
        case paInt8:
            if( ifClip && ifDither ) proc = (PortAudioConverter *) PaConvert_Float32_Int8_ClipDither;
            else if( ifClip ) proc = (PortAudioConverter *) PaConvert_Float32_Int8_Clip;
            else if( ifDither ) proc = (PortAudioConverter *) PaConvert_Float32_Int8_Dither;
            else proc = (PortAudioConverter *) PaConvert_Float32_Int8;
            break;
        case paInt16:
            if( ifClip && ifDither ) proc = (PortAudioConverter *) PaConvert_Float32_Int16_ClipDither;
            else if( ifClip ) proc = (PortAudioConverter *) PaConvert_Float32_Int16_Clip;
            else if( ifDither ) proc = (PortAudioConverter *) PaConvert_Float32_Int16_Dither;
            else proc = (PortAudioConverter *) PaConvert_Float32_Int16;
            break;
        case paInt32:
            /* Don't bother dithering a 32 bit integer! */
            if( ifClip ) proc = (PortAudioConverter *) PaConvert_Float32_Int32_Clip;
            else proc = (PortAudioConverter *) PaConvert_Float32_Int32;
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
    return proc;
            
}

/*************************************************************************/
PaError PaConvert_SetupInput( internalPortAudioStream   *past,
    PaSampleFormat   nativeInputSampleFormat )
{    
    past->past_NativeInputSampleFormat = nativeInputSampleFormat;
    past->past_InputConversionSourceStride = 1;
    past->past_InputConversionTargetStride = 1;
    
    if( nativeInputSampleFormat != past->past_InputSampleFormat )
    {
        int ifDither = (past->past_Flags & paDitherOff) == 0;
        past->past_InputConversionProc = PaConvert_SelectProc( nativeInputSampleFormat,
             past->past_InputSampleFormat, 0, ifDither );
        if( past->past_InputConversionProc == NULL ) return paSampleFormatNotSupported;
    }
    else
    {
        past->past_InputConversionProc = NULL; /* no conversion necessary */
    }
    
    return paNoError;
}

/*************************************************************************/
PaError PaConvert_SetupOutput( internalPortAudioStream   *past,
    PaSampleFormat   nativeOutputSampleFormat )
{

    past->past_NativeOutputSampleFormat = nativeOutputSampleFormat;
    past->past_OutputConversionSourceStride = 1;
    past->past_OutputConversionTargetStride = 1;
    
    if( nativeOutputSampleFormat != past->past_OutputSampleFormat )
    {
        int ifDither = (past->past_Flags & paDitherOff) == 0;
        int ifClip = (past->past_Flags & paClipOff) == 0;

        past->past_OutputConversionProc = PaConvert_SelectProc( past->past_OutputSampleFormat,
            nativeOutputSampleFormat, ifClip, ifDither );
        if( past->past_OutputConversionProc == NULL ) return paSampleFormatNotSupported;
    }
    else
    {
        past->past_OutputConversionProc = NULL; /* no conversion necessary */
    }
    
    return paNoError;
}

/*************************************************************************
** Called by host code.
** Convert input from native format to user format,
** call user code,
** then convert output to native format.
** Returns result from user callback.
*/
long PaConvert_Process( internalPortAudioStream   *past,
                            void *nativeInputBuffer,
                            void *nativeOutputBuffer )
{
    int               userResult;
    void             *inputBuffer = NULL;
    void             *outputBuffer = NULL;

    /* Get native input data. */
    if( (past->past_NumInputChannels > 0) && (nativeInputBuffer != NULL) )
    {
        if( past->past_InputSampleFormat == past->past_NativeInputSampleFormat )
        {
        /*  Already in native format so just read directly from native buffer. */
            inputBuffer =  nativeInputBuffer;
        }
        else
        {
            inputBuffer = past->past_InputBuffer;
        /* Convert input data to user format. */
            (*past->past_InputConversionProc)(nativeInputBuffer, past->past_InputConversionSourceStride,
                inputBuffer, past->past_InputConversionTargetStride,
                past->past_FramesPerUserBuffer * past->past_NumInputChannels );
        }
    }

    /* Are we doing output? */
    if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) )
    {
        outputBuffer = (past->past_OutputConversionProc == NULL) ?
                       nativeOutputBuffer : past->past_OutputBuffer;
    }
    /*
     AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer );
     AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer );
    */
    /* Call user callback routine. */
    userResult = past->past_Callback(
                     inputBuffer,
                     outputBuffer,
                     past->past_FramesPerUserBuffer,
                     past->past_FrameCount,
                     past->past_UserData );

    /* Advance frame counter for timestamp. */
    past->past_FrameCount += past->past_FramesPerUserBuffer; // FIXME - should this be in here?

    /* Convert to native format if necessary. */
    if( (past->past_OutputConversionProc != NULL ) && (outputBuffer != NULL) )
    {
        (*past->past_OutputConversionProc)( outputBuffer, past->past_OutputConversionSourceStride,
            nativeOutputBuffer, past->past_OutputConversionTargetStride,
            past->past_FramesPerUserBuffer * past->past_NumOutputChannels );
    }

    return userResult;
}