/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
/*
 * $Id: mas_channelconv_device.c,v 1.2 2003/03/17 19:02:13 rocko Exp $
 *
 * Copyright (c) 2000, 2001 by Shiman Associates Inc. and Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions: The above
 * copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors or
 * copyright holders shall not be used in advertising or otherwise to
 * promote the sale, use or other dealings in this Software without
 * prior written authorization from the authors or copyright holders,
 * as applicable.
 *
 * * All trademarks and registered trademarks mentioned herein are the
 * property of their respective owners. No right, title or interest in
 * or to any trademark, service mark, logo or trade name of the
 * authors or copyright holders or their licensors is granted.
 *
 */

/* 2 OCT 2002 - rocko - verified reentrant
 * 2 OCT 2002 - rocko - verified timestamp clean
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "mas/mas_dpi.h"
#include "profile.h"

#define STATIC_SEGMENT_LENGTH 10240

/*********************************************************************
 ** NOTE
 **
 ** This device requires that input packets contain an integer number
 ** of complete stereo or mono samples!!  For 44100/16/2, this means
 ** packet sizes must be integer multiples of 4.
 **
 *********************************************************************/

/************************************************************************
 * channelconv_state
 *
 * State memory structure for this device instance.
 *
 ************************************************************************/
struct channelconv_state
{
    int32 reaction;
    int32 data_sink;
    int32 data_source;
    int8  got_source, got_sink;
    uint8  inchan, outchan;
    uint8  inres, outres;
    uint8  informat, outformat; /* 0 ulinear, 1 linear */
    int    inbpstc;
    float  out_size_multiple;
    int    malloc_warned;
    
    void    (*convert)();
};

/*************************************************************************
 * ACTIONS
 *************************************************************************/


/* standard actions ****************************************************/
int32 mas_dev_init_instance( int32 , void* );
int32 mas_dev_terminate( int32 , void* );
int32 mas_dev_show_state( int32 device_instance, void* predicate );

/* device specific actions *********************************************/
int32 mas_channelconv_convert( int32 , void* );


/* local functions */
static void mtos8( char *in, char *out, uint16 *len );
static void mtos16( char *in, char *out, uint16 *len );
static void mtos32( char *in, char *out, uint16 *len );
static void stom8( char *in, char *out, uint16 *len );
static void stomu8( char *in, char *out, uint16 *len );
static void stom16( char *in, char *out, uint16 *len );
static void stomu16( char *in, char *out, uint16 *len );
static void stom32( char *in, char *out, uint16 *len );
static void no_op( char *in, char *out, uint16 *len );


/***************************************************************************
 * mas_dev_init_instance - standard device action
 *
 *  predicate: unused
 *
 * Initializes state structure.
 *  
 * returns: error
 *
 ***************************************************************************/
int32
mas_dev_init_instance( int32 device_instance, void* predicate )
{
    struct channelconv_state*  state;
    
    /* Allocate state holder and cast it so we can work on it */
    state       = MAS_NEW( state );
    if ( state == 0 )
	return mas_error(MERR_MEMORY);

    masd_set_state(device_instance, state); /* set device state */
    
    masd_get_port_by_name( device_instance, "sink", &state->data_sink );
    masd_get_port_by_name( device_instance, "source", &state->data_source );
    masd_get_port_by_name( device_instance, "reaction", &state->reaction );
    state->out_size_multiple = 1.0;

    return 0;
}


/***************************************************************************
 * mas_dev_configure_port
 *
 *  predicate: int32 portnum
 *
 * 
 *  
 * returns: error
 *
 ***************************************************************************/
int32
mas_dev_configure_port( int32 device_instance, void* predicate )
{
    struct channelconv_state *state;
    struct mas_data_characteristic *dc;
    int32 portnum = *(int32*)predicate;
    int32 *dataflow_port_dependency;
    uint32 srate;
    uint8 endian;
    int32 err;
        
    MASD_GET_STATE(device_instance, state);

    err = masd_get_data_characteristic( *(int32*)predicate, &dc );
    if ( err < 0 )
        return mas_error(MERR_INVALID);

    /* retrieve format information from data characteristic */
    if ( portnum == state->data_sink )
    {
        err = masc_scan_audio_basic_dc( dc, &state->informat, &srate, &state->inres, &state->inchan, &endian );
        if ( err < 0 )
            return mas_error(MERR_INVALID);
        state->inbpstc = masc_get_audio_basic_bpstc( state->inres, state->inchan );
        state->got_sink = TRUE;    
    }
    else if ( portnum == state->data_source )
    {
        err = masc_scan_audio_basic_dc( dc, &state->outformat, &srate, &state->outres, &state->outchan, &endian );
        if ( err < 0 )
            return mas_error(MERR_INVALID);
        
        state->got_source = TRUE;
    }
    else
    {
        return mas_error(MERR_INVALID);
    }
    

    if ( (state->got_source) && (state->got_sink) )
    {
        /* sanity checks */
        if (state->inres != state->outres){
            return mas_error(MERR_INVALID);
        }
        if (state->informat != state->outformat){
            return mas_error(MERR_INVALID);
        }
        
        if ( (state->inchan==1)&&(state->outchan==2) )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "channelconv: converting mono to stereo, %d-bit samples.", state->inres );
            state->out_size_multiple = 2.0;
            if (state->inres==8)
            {
                state->convert = &mtos8;
            }
            else
            {
                if (state->inres==16)
                {
                    state->convert = &mtos16;
                }
                else
                {
                    /* 20, 24 bit */
                    state->convert = &mtos32;
                }
            }
        }
        else if ( (state->inchan==2)&&(state->outchan==1) )
        {
            state->out_size_multiple = 0.5;
            masc_log_message( MAS_VERBLVL_DEBUG, "channelconv: converting stereo to mono, %d-bit samples.", state->inres );
            if (state->inres==8)
            {
                if( state->informat == MAS_LINEAR_FMT )
                {
                    state->convert = &stom8;
                }
                else
                {
                    state->convert = &stomu8;
                }
            }
            else
            {
                if (state->inres==16)
                {
                    if( state->informat == MAS_LINEAR_FMT)
                    {
                        state->convert = &stom16;
                    }
                    else
                    {
                        state->convert = &stomu16;
                    }
                }
                else
                {
                    /* 20, 24 bit */
                    state->convert = &stom32;
                }
            }
        }
        else
        {
            /* who would do THAT? */
            state->convert = &no_op;
            state->out_size_multiple = 1.0;
            masc_log_message( MAS_VERBLVL_DEBUG, "channelconv: configured for no channel conversion, %d-bit samples.", state->inres );
        }

        /* schedule our dataflow dependency on data_sink */
        dataflow_port_dependency = masc_rtalloc( sizeof (int32) );
        *dataflow_port_dependency = state->data_sink;
        err = masd_reaction_queue_action(state->reaction, device_instance, 
                                         "mas_channelconv_convert",0,0,0,0,0,
                                         MAS_PRIORITY_DATAFLOW, 1, 1, 
                                         dataflow_port_dependency);
        if ( err < 0 ) return err;
    }

    return 0;
}

int32
mas_dev_disconnect_port( int32 device_instance, void* predicate )
{
    struct channelconv_state*  state;
    int32 portnum = *(int32*)predicate;

    masd_get_state(device_instance, (void**)&state);

    if ( portnum == state->data_sink )
    {
        state->got_sink = FALSE;
        state->malloc_warned = FALSE;
        state->out_size_multiple = 1.0;
    }
    else if ( portnum == state->data_source )
    {
        state->got_source = FALSE;
        state->malloc_warned = FALSE;
        state->out_size_multiple = 1.0;
    }
    else
    {
        return mas_error(MERR_INVALID);
    }
    
    return 0;
}

int32 mas_dev_terminate( int32 device_instance, void* predicate )
{
    return 0;
}

int32
mas_dev_exit_instance( int32 device_instance, void* predicate )
{
    struct channelconv_state*  state;
    
    masd_get_state(device_instance, (void**)&state);
    masc_rtfree( state );
    
    return 0;
}

int32
mas_dev_show_state( int32 device_instance, void* predicate )
{
    struct channelconv_state*  state;
    
    masd_get_state(device_instance, (void**)&state);

    masc_log_message( 0, "channelconv device... yes I'm here doing work.", device_instance );
    return 0;
}


/***************************************************************************
 * mas_channelconv_convert - read & print contents of sink
 *
 *  predicate: unused
 *
 * Sends mas_data information to stdout.
 *  
 * returns: error
 *
 ***************************************************************************/
int32
mas_channelconv_convert( int32 device_instance, void* predicate )
{
    struct channelconv_state*   s;
    struct mas_data*    data;
    uint16 len;
    char static_buffer[STATIC_SEGMENT_LENGTH];
    char *dynamic_buffer;

    masd_get_state(device_instance, (void**)&s);
    masd_get_data( s->data_sink, &data );
        
    len = data->length;

    /* sanity check */
    if ( s->inbpstc * (data->length / s->inbpstc) != data->length )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "channelconv: Received data segment with length not a multiple of the channels times bytes per sample!" );
        return mas_error(MERR_INVALID);
    }

    /* Is the output data segment longer than the input? */
    if ( s->out_size_multiple > 1.0 )
    {
        /* See if we have enough room in the data segment to do the
           operation without allocating additional memory. */
        if ( ( data->allocated_length < data->length * s->out_size_multiple ) || ( sizeof static_buffer < data->length * s->out_size_multiple ) )
        {
            /* We can't fit the output back in the data segment and we
               don't have enough memory in the static buffer to allow
               copying.*/

            /* Deliver a one-time warning.  malloc_warned is reset
               only on port disconnect. */
            if ( ! s->malloc_warned )
            {
                masc_log_message( MAS_VERBLVL_WARNING, "channelconv: [warning] Allocating memory for each packet; works, but will be slower.  Reason follows:" );
                if ( sizeof static_buffer < data->length )
                {
                    masc_log_message( MAS_VERBLVL_WARNING, "channelconv: [warning] Requested data segment length %d exceeds temporary segment length %d.", data->length * s->out_size_multiple, sizeof static_buffer );
                    masc_log_message( MAS_VERBLVL_WARNING, "channelconv: [warning] To fix: increase temporary segment length in channelconv device." );
                }
                else
                {
                    masc_log_message( MAS_VERBLVL_WARNING, "channelconv: [warning] Not enough allocated memory in input data segment to reuse it." );
                    masc_log_message( MAS_VERBLVL_WARNING, "channelconv: [warning] Have %d bytes, need %d bytes.", data->allocated_length, (int)(data->length * s->out_size_multiple) );
                    masc_log_message( MAS_VERBLVL_WARNING, "channelconv: [warning] To fix: increase allocated size of data segments." );
                }

                s->malloc_warned = TRUE;
            }

            /* Allocate new data segment, with a 50% safety margin. */
            len = data->length * s->out_size_multiple * 1.5;
            data->allocated_length = len;
            dynamic_buffer = masc_rtalloc( len );
            if ( dynamic_buffer == NULL )
                return mas_error(MERR_MEMORY);
            
            /* do the conversion, placing the result in the newly
               allocated buffer.  data->length will be modified to reflect
               the actual length. */
            s->convert( data->segment, dynamic_buffer, &data->length);
            
            /* toss the old data segment */
            masc_rtfree( data->segment );
            
            /* use the newly allocated data segment */
            data->segment = dynamic_buffer;

        }
        else
        {
            /* copy the input data to our static buffer */
            memcpy( static_buffer, data->segment, data->length );
            
            /* Do the conversion, using the static buffer as input,
               dumping the result to the data segment.  data->length will
               be modified to reflect the actual length. */
            s->convert( static_buffer, data->segment, &data->length );
        }
    }
    else /* out_size_multiple <= 1.0 */
    {
        /* We can do the conversion in-place.  data->length will be
           modified to reflect the actual length. */
        s->convert( data->segment, data->segment, &data->length );
    }

    masd_post_data( s->data_source, data );

    return 0;
}


void mtos8( char *in, char *out, uint16 *len )
{
    int i;
    uint16 length;
    
    length = *len;
    
    for (i=0; i<length; i++)
    {
        out[2*i] = out[2*i+1] = in[i];
    }
    *len = *len * 2;
}


void mtos16( char *in, char *out, uint16 *len )
{
    uint16 *a;
    uint16 *b;
    int i;
    uint16 length;
    
    length = *len;
    a = (uint16*)in;
    b = (uint16*)out;

    for (i=0; i<length/2; i++)
    {
        b[2*i] = b[2*i+1] = a[i];
    }
    *len = *len * 2;
}


void mtos32( char *in, char *out, uint16 *len )
{
    uint32 *a;
    uint32 *b;
    int i;
    uint16 length;
    
    length = *len;
    a = (uint32*)in;
    b = (uint32*)out;

    for (i=0; i<length/4; i++)
    {
        b[2*i] = b[2*i+1] = a[i];
    }
    *len = *len * 2;
}

void stom8( char *in, char *out, uint16 *len )
{
    int8 *a;
    int8 *b;
    int i;
    uint16 length;
    int16 tmp;

    *len /= 2;
    length = *len;
    b = (int8*) out;
    a = (int8*) in;
    
    for (i=0; i<length; i++)
    {
        tmp =  a[2*i];
        tmp += a[2*i+1];

        tmp /= 2;
        
        b[i] = tmp;
    }
}

void stomu8( char *in, char *out, uint16 *len )
{
    uint8 *a;
    uint8 *b;
    int i;
    uint16 length;
    uint16 tmp;
    
    *len /= 2;
    length = *len;
    b = (uint8*) out;
    a = (uint8*) in;
    
    for (i=0; i<length; i++)
    {
        tmp =  a[2*i];
        tmp += a[2*i+1];

        tmp = tmp >> 1;
        
        b[i] = tmp;
    }
}

void stom16( char *in, char *out, uint16 *len )
{
    int16 *a;
    int16 *b;
    int i;
    uint16 length;
    int32 tmp;
    
    *len /= 2;
    length = *len;
    b = (int16*) out;
    a = (int16*) in;

    
    for (i=0; i<length/2; i++)
    {
        tmp =  a[2*i];
        tmp += a[2*i+1];
        
        tmp /= 2;
        b[i] = tmp;
    }
}

void stomu16( char *in, char *out, uint16 *len )
{
    uint16 *a;
    uint16 *b;
    int i;
    uint16 length;
    uint32 tmp;
    
    *len /= 2;
    length = *len;
    b = (uint16*) out;
    a = (uint16*) in;
    
    for (i=0; i<length/2; i++)
    {
        tmp =  a[2*i];
        tmp += a[2*i+1];
        
        tmp = tmp >> 1;
        
        b[i] = tmp;
    }
}


void stom32( char *in, char *out, uint16 *len )
{
    int32 *a;
    int32 *b;
    int i;
    uint16 length;
    
    *len /= 2;
    length = *len;
    b = (int32*) out;
    a = (int32*) in;

    /* does not handle > 30 bit */
    for (i=0; i<length/4; i++)
    {
        b[i]  = a[2*i];
        b[i] += a[2*i+1];
        b[i] /= 2;
    }
}


void no_op( char *in, char *out, uint16 *len )
{
    memcpy(out, in, *len);   
}
