/*
 * 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.
 *
 */
/*
 * 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.
 *
 */

/* 
 * $Id: solaris.c,v 1.3 2003/10/07 14:10:04 silvio Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/audio.h>
#include <sys/audioio.h>
#include <sys/types.h>
#include <stropts.h>
#include <sys/conf.h>
#include <math.h>
#include "mas/mas_dpi.h"
#include "anx_internal.h"
#include "fd_device.h"
#include "solaris_profile.h"


/*************************************************************************
 * LOCAL PROTOTYPES
 *************************************************************************/
static int32 get_device_caps( struct anx_state* state );
static int32 solaris_add_mix_channel(struct anx_state* state, char* name, int32 portnum, int iodir, uint_t solaris_ap_num);
static int32 solaris_get_info( struct solaris_audio_state* pdst );
static int32 solaris_set_info( struct solaris_audio_state* pdst );

/* BEGIN PLATFORM DEPENDENT ACTIONS ***********************************/
int32
pdanx_init_library( void )
{
    return 0;
}

int32
pdanx_exit_library( void )
{
    return 0;
}

int32
pdanx_init_instance( struct anx_state* state, void* predicate )
{
    int                      holder;
    int32                    err;

    /* open audio device */
    state->pdstate.fd = open_audio_device_fd( state, "/dev/audio" );
    if ( state->pdstate.fd < 0 )
        return mas_error(MERR_FILE_CANNOT_OPEN);

    if (ioctl(state->pdstate.fd, AUDIO_GETDEV, &state->pdstate.device) < 0)
    {
        masc_log_message( MAS_VERBLVL_ERROR, "can't query audio device");
        return mas_error(MERR_IO);
    }

    masc_log_message( MAS_VERBLVL_DEBUG, "-- DEVICE -------------------------------------------------");
    masc_log_message( MAS_VERBLVL_DEBUG, "device name: %s", state->pdstate.device.name);
    masc_log_message( MAS_VERBLVL_DEBUG, "    version: %s", state->pdstate.device.version);
    masc_log_message( MAS_VERBLVL_DEBUG, "     config: %s", state->pdstate.device.config);

    /* get device capabilities */
    err = get_device_caps( state );
    if ( err < 0 ) return err;
    
    return 0;
}

int32
pdanx_exit_instance( struct anx_state* state, void* predicate )
{
    return 0;
}

/* Handle state transitions.  Open and re-configure the device if
 * transitioning from inactive/inactive_pending to
 * active/active_pending. */
int32 pdanx_change_res_state( struct anx_state *state, enum res_state res_state )
{
	return 0;
}

int32
pdanx_configure_port( struct anx_state* state, int32 portnum, struct mas_data_characteristic* dc )
{
	int32			err;
	struct audio_prinfo	*play_info, *rec_info;
	int			target_time_ms;
	uint8			format;
	uint8			resolution;
	uint8			channels;
	u_int16			sample_rate; 
	u_int16			bpstc;		/* bytes per sample times channels */
	int			no_ioctls = FALSE;
	struct audio_info	*tinfo;		/* temporary info */
	int32			exit_status;
        struct mas_data_characteristic* odc;

	masc_entering_log_level("Configuring port: pdanx_configure_port()");

	play_info = &state->pdstate.info.play;
	rec_info = &state->pdstate.info.record;
	
	/* set the playback device */
	format = state->play_format;
	resolution = state->play_resolution;
	channels = state->play_channels;
	sample_rate = state->play_sample_rate;
	bpstc = state->play_bpstc;
	
	/* Setup both input and output on the input request */
	if ( portnum == state->audio_sink )
	{
		/* pause and flush output queues */
		solaris_get_info ( &state->pdstate );
		state->pdstate.info.output_muted = FALSE;
		state->pdstate.info.play.error = FALSE;
		state->pdstate.info.play.pause = TRUE;
		solaris_set_info( &state->pdstate );

		if(ioctl( state->pdstate.fd, I_FLUSH, FLUSHW) < 0)
		{
			masc_log_message( MAS_VERBLVL_ERROR, "can't flush write queue.");
			exit_status = mas_error(MERR_IO);
			goto failure;
		}
		play_info->buffer_size = PLAY_BUFFER_SIZE_MS * sample_rate * bpstc * 0.001;
		
		
		/* pause and flush input queues */
		solaris_get_info ( &state->pdstate );
		state->pdstate.info.record.error = FALSE;
		state->pdstate.info.record.pause = TRUE;
		solaris_set_info( &state->pdstate );

		if(ioctl( state->pdstate.fd, I_FLUSH, FLUSHR) < 0)
		{
			masc_log_message( MAS_VERBLVL_ERROR, "can't flush read queue.");
			exit_status = mas_error(MERR_IO);
			goto failure;
		}
		rec_info->buffer_size = REC_BUFFER_SIZE_MS * sample_rate * bpstc * 0.001;
	}

	/* pick up the format settings */
	switch ( format )
	{
	case MAS_ULAW_FMT:
		play_info->encoding = AUDIO_ENCODING_ULAW;
		rec_info->encoding = AUDIO_ENCODING_ULAW;
		break;
	case MAS_ALAW_FMT:
		play_info->encoding = AUDIO_ENCODING_ALAW;
		rec_info->encoding = AUDIO_ENCODING_ALAW;
		break;
	case MAS_LINEAR_FMT:
		play_info->encoding = AUDIO_ENCODING_LINEAR;
		rec_info->encoding = AUDIO_ENCODING_LINEAR;
		break;
	case MAS_ULINEAR_FMT:
		mas_error(MERR_INVALID);
		break;
	default:
		exit_status = mas_error(MERR_INVALID);
		goto failure;
	}
	
	play_info->precision = resolution;
	play_info->channels = channels;
	play_info->sample_rate = sample_rate;
	rec_info->precision = resolution;
	rec_info->channels = channels;
	rec_info->sample_rate = sample_rate;
	
	err = solaris_set_info( &state->pdstate );
	if ( err < 0 )
	{
		exit_status = err;
		goto failure;
	}
	
	/* Configure source/sink dependent stuff.  NOTE: this sets the
	 * data characteristic of the other port.  For now, this keeps
	 * both formats the same.  Eventually, this should be
	 * changed. */

        /* make a copy of the dc for the other port */
        odc = MAS_NEW( odc );
        masc_setup_dc( odc, dc->numkeys );
        masc_copy_dc( odc, dc );

	if ( portnum == state->audio_sink )
	{
		if ( !state->source_active )
			masd_set_data_characteristic( state->audio_source, odc );
	
		/* make the buffer */
		err = anx_make_buffer( &state->play_buffer, PLAY_BUFFER_SIZE, DEFAULT_FILL_LINE_US * sample_rate * bpstc / 1000000.0 );
		if ( err < 0 )
		{
			exit_status = err;
			goto failure;
		}
		masc_log_message( MAS_VERBLVL_DEBUG, "filling %fms buffer", 1000.0 * (float) state->play_buffer->fill_line / (sample_rate * bpstc ) );
	}
	else
	{
		if ( !state->sink_active )
			masd_set_data_characteristic( state->audio_sink, odc );
	    
		/* make the buffer */
		anx_make_buffer( &state->pdstate.recbuf, REC_BUFFER_SIZE, DEFAULT_FILL_LINE_US * sample_rate * bpstc / 1000000.0 );
	}

	exit_status = 0;
	goto success;
failure:
success:
	masc_exiting_log_level();
	return exit_status;
}

int32
pdanx_disconnect_port( struct anx_state* state, int32 portnum )
{
    struct mas_data_characteristic *dc, *odc;
    int32 err;
    
    /* set the data characteristic of the other port */
    if ( portnum == state->audio_sink )
    {
        /* re-set the dc of this port to the currently configured one */
        err = masd_get_data_characteristic( state->audio_source, &dc );
        if ( err >= 0 )
        {
            odc = MAS_NEW( odc );
            masc_setup_dc( odc, dc->numkeys );
            masc_copy_dc( odc, dc );
            masd_set_data_characteristic( state->audio_sink, odc );
        }
    }
    else if ( portnum == state->audio_source )
    {
        /* re-set the dc of this port to the currently configured one. */
        err = masd_get_data_characteristic( state->audio_sink, &dc );
        
        if ( err >= 0 )
        {
            odc = MAS_NEW( odc );
            masc_setup_dc( odc, dc->numkeys );
            masc_copy_dc( odc, dc );
            masd_set_data_characteristic( state->audio_source, odc );
        }
    }
    
    return 0;
}

int32 
pdanx_playback_start( struct anx_state* state )
{
    /* unpause output queues */
    solaris_get_info ( &state->pdstate );
    state->pdstate.info.play.error = FALSE;
    state->pdstate.info.play.pause = FALSE;
    solaris_set_info( &state->pdstate );

    return 0;
}

int32 
pdanx_playback_stop( struct anx_state* state )
{
    /* pause and flush output queues */
    solaris_get_info ( &state->pdstate );
    state->pdstate.info.play.error = FALSE;
    state->pdstate.info.play.pause = TRUE;
    solaris_set_info( &state->pdstate );
    if( ioctl( state->pdstate.fd, I_FLUSH, FLUSHW) < 0)
    {
	masc_log_message( MAS_VERBLVL_ERROR, "can't flush write queue. pdanx_playback_stop()");
	return mas_error(MERR_IO);
    }

    return 0;
}

int32 
pdanx_playback_pause( struct anx_state* state )
{
    /* pause and flush output queues */
    solaris_get_info ( &state->pdstate );
    state->pdstate.info.play.error = FALSE;
    state->pdstate.info.play.pause = TRUE;
    solaris_set_info( &state->pdstate );
    if(ioctl( state->pdstate.fd, I_FLUSH, FLUSHW) < 0 )
    {
	masc_log_message( MAS_VERBLVL_ERROR, "can't flush write queue. pdanx_playback_stop()");
	return mas_error(MERR_IO);
    }

    return 0;
}

int32 
pdanx_playback_poll( struct anx_state* state, struct mas_data* data )
{
    struct solaris_audio_state*        pdst = &state->pdstate;
    int                      error;
    int32                    err;
    int report_xrun = 0;
    struct audio_info info;
    
    
    /** we've got to fill the buffer */
    if (state->play_buffer->filling)
    {
        err = anx_buffer_append( state->play_buffer, data->segment, data->length );
        masc_log_message(MAS_VERBLVL_DEBUG, "filling buffer - %%%d percent full",
                         100 * state->play_buffer->pos/state->play_buffer->fill_line );
        if ( err < 0) return err;
    }

    /* catches changes from above, not an else */
    if ( !state->play_buffer->filling )
    {
	/* write anything in the buffer */
	if (state->play_buffer->pos > 0)
	{
            state->mcref = state->mcnow;
            state->mtref = data->header.media_timestamp - ( state->play_buffer->pos - data->length ) / state->play_bpstc;
            state->valid_refmark = TRUE;

	    if (( error = write (pdst->fd, state->play_buffer->contents, state->play_buffer->pos)) != state->play_buffer->pos )
		return mas_error(MERR_IO);
    	    state->played_bytes += state->play_buffer->pos;
            anx_reset_buffer( state->play_buffer );
	}
        else 
        {
            /* nothing in buffer - write this data's data */
            if (( error = write (pdst->fd, data->segment, data->length)) != data->length )
                return mas_error(MERR_IO);
            state->played_bytes += data->length;        
        }
    }

    if (report_xrun) 
	return mas_error(MERR_XRUN);
    return 0;
}

int32 
pdanx_record_start( struct anx_state* state )
{
    struct solaris_audio_state*  pdst = &state->pdstate;
    int32 err;
    struct audio_info info;

    AUDIO_INITINFO(&info);
    pdst->info.record.error = FALSE;
    pdst->info.record.pause = TRUE;
    solaris_set_info( pdst );

    /* If we were recording before, stopped, and have now restarted,
       there might be  junk in the buffer.  Flush it. */
    if( ioctl( pdst->fd, I_FLUSH, FLUSHR) < 0)
    {
	masc_log_message( MAS_VERBLVL_ERROR, "can't flush read queue. pdanx_record_start()");
	return mas_error(MERR_IO);
    }

    AUDIO_INITINFO(&info);
    pdst->info.record.pause = FALSE;
    solaris_set_info( pdst );

    return 0;
}

int32 
pdanx_record_stop( struct anx_state* state )
{
    int32 err;
    
    err = solaris_get_info( & state->pdstate );
    if ( err < 0 ) return err;

    state->pdstate.info.record.pause = TRUE;

    err = solaris_set_info( & state->pdstate );
    if ( err < 0 ) return err;
    
    return 0;
}

int32 
pdanx_record_pause( struct anx_state* state )
{
    int32 err;

    err = solaris_get_info( & state->pdstate );
    if ( err < 0 ) return err;

    state->pdstate.info.record.pause = TRUE;

    err = solaris_set_info( & state->pdstate );
    if ( err < 0 ) return err;
    
    return 0;
}

int32 
pdanx_record_poll( struct anx_state* state, struct mas_data** data_ptr )
{
    struct solaris_audio_state*  pdst = &state->pdstate;
    struct mas_data* data;
    int nmesg;
    static uint32 lastcnt = 0;
    int gotlen;
    int checkbuf = FALSE;
    static int nread = 0; /* remember nread */
    int try_one_more = FALSE;
    
    *data_ptr = 0;

    do
    {
        /* stop looping */
        if ( try_one_more == TRUE ) try_one_more = FALSE;
        
        /* save time: only do this if we need to */
        if ( nread < MAS_ANX_SEGLEN )
        {
            /* can we read anything?  avoids blocking - could just use
             * non-blocking I/O, dummy. */
            if ( nmesg = ioctl(pdst->fd, I_NREAD, &nread ) < 0 )
                return mas_error(MERR_IO);
        }
    
#if 0
        solaris_get_info ( pdst );
        if ( pdst->info.record.samples - MAS_ANX_SEGLEN/state->rec_bpstc >= lastcnt )
        {
            lastcnt = pdst->info.record.samples;
            nread = MAS_ANX_SEGLEN;
        }
#endif

        /* number of bytes to read is the smaller of:
         *   1. whatever's available
         *   2. What we need to make a complete data segment with length MAS_ANX_SEGLEN
         */
        nread = min(nread, ( MAS_ANX_SEGLEN - ( pdst->recbuf->pos - pdst->recbuf->head ) ));

        /* Either there's data waiting to be read or we haven't started
           the recording process yet.  In the latter case, force a read. */
        if ( (nread > 0) || (state->rec_state == START_STATE) ) 
        {
	    data = MAS_NEW(data);
            masc_setup_data( data, MAS_ANX_SEGLEN );
        
            gotlen = read (pdst->fd, data->segment, nread);
            nread -= gotlen;
            /* We read less than MAS_ANX_SEGLEN, either on purpose, or
               by accident (read returned short). */
            if ( gotlen < MAS_ANX_SEGLEN && gotlen > 0 )
            {
                /* It's not a complete segment, so add this stuff to
                 * the buffer */
                anx_buffer_append( pdst->recbuf, data->segment, gotlen );

                /* Is there enough in the buffer now for a complete
                   segment? */
                if ( pdst->recbuf->pos - pdst->recbuf->head >= MAS_ANX_SEGLEN )
                {
                    /* yes, copy into our data struct */
                    memcpy( data->segment, pdst->recbuf->contents, MAS_ANX_SEGLEN );
                    pdst->recbuf->head += MAS_ANX_SEGLEN;

                    /*** THIS COULD BE REMOVED, I THINK */
                    /* this shouldn't happen, but if we have a remainder,
                       move it back to the beginning of the buffer */
                    if ( pdst->recbuf->pos - pdst->recbuf->head )
                    {
                        memmove( pdst->recbuf->contents, pdst->recbuf->contents + pdst->recbuf->head, pdst->recbuf->pos - pdst->recbuf->head );
                        pdst->recbuf->pos = pdst->recbuf->pos - pdst->recbuf->head;
                        pdst->recbuf->head = 0;
                    }
                    else
                    {
                        /* reset the stuff */
                        anx_reset_buffer( pdst->recbuf );
                    }

                    try_one_more = FALSE;
                }
                else /* there isn't enough in the buffer for a full segment */
                {
                    /* give up ? */
                    if ( try_one_more == TRUE )
                    {
                        try_one_more = FALSE;
                        /* no data this call */
                        masc_strike_data( data );
                        data = 0;
                    }
                    else
                    {
                        /* Sometimes, the audio driver will report a
                         * short bit of audio actually remaining when,
                         * in fact, there's a lot more there.  Instead
                         * of bailing now and waiting for another
                         * action, lets try to make a complete data
                         * segment right now. */
                        try_one_more = TRUE;
                    }
                    
                }
            }
            else /* didn't read any data */
            {
                try_one_more = FALSE;
            }

            *data_ptr = data;
        }
    } while ( try_one_more );
    
    return 0;
}

int32
pdanx_show_state( struct anx_state* anxstate )
{
    struct solaris_audio_state* state = &anxstate->pdstate;
        
    masc_log_message(0, "*-- platform dependent anx state -------------------------------\n");
    masc_log_message( 0, "---------------------");
    masc_log_message( 0, "monitor mix gain: %d", state->info.monitor_gain);
    masc_log_message( 0, "    output muted: %s", (state->info.output_muted == 0)?("NO"):("YES"));
    masc_log_message( 0, "     hw features: 0x%04X", state->info.hw_features);
    masc_log_message( 0, "     sw features: 0x%04X", state->info.sw_features);
    masc_log_message( 0, " enabled sw feat: 0x%04X", state->info.sw_features_enabled);

    masc_log_message( 0, "-- PLAYBACK ---------");
    masc_log_message( 0, "sample rate: %d", state->info.play.sample_rate);
    masc_log_message( 0, "   channels: %d", state->info.play.channels);
    masc_log_message( 0, "  precision: %d", state->info.play.precision);
    masc_log_message( 0, "   encoding: %d", state->info.play.encoding);
    masc_log_message( 0, "       gain: %d", state->info.play.gain);
    masc_log_message( 0, "buffer size: %d", state->info.play.buffer_size);
    masc_log_message( 0, "       port: %d", state->info.play.port);
    masc_log_message( 0, "    samples: %d", state->info.play.samples);
    masc_log_message( 0, "        eof: %d", state->info.play.eof);
    masc_log_message( 0, "      pause: %d", state->info.play.pause);
    masc_log_message( 0, "      error: %d", state->info.play.error);
    masc_log_message( 0, "    waiting: %d", state->info.play.waiting);
    masc_log_message( 0, "    balance: %d", state->info.play.balance);
    masc_log_message( 0, "       open: %d", state->info.play.open);
    masc_log_message( 0, "     active: %d", state->info.play.active);
    masc_log_message( 0, "avail ports: 0x%04X", state->info.play.avail_ports);
    masc_log_message( 0, "  mod ports: 0x%04X", state->info.play.mod_ports);

    masc_log_message( 0, "-- RECORD ---------");
    masc_log_message( 0, "sample rate: %d", state->info.record.sample_rate);
    masc_log_message( 0, "   channels: %d", state->info.record.channels);
    masc_log_message( 0, "  precision: %d", state->info.record.precision);
    masc_log_message( 0, "   encoding: %d", state->info.record.encoding);
    masc_log_message( 0, "       gain: %d", state->info.record.gain);
    masc_log_message( 0, "buffer size: %d", state->info.record.buffer_size);
    masc_log_message( 0, "       port: %d", state->info.record.port);
    masc_log_message( 0, "    samples: %d", state->info.record.samples);
    masc_log_message( 0, "        eof: %d", state->info.record.eof);
    masc_log_message( 0, "      pause: %d", state->info.record.pause);
    masc_log_message( 0, "      error: %d", state->info.record.error);
    masc_log_message( 0, "    waiting: %d", state->info.record.waiting);
    masc_log_message( 0, "    balance: %d", state->info.record.balance);
    masc_log_message( 0, "       open: %d", state->info.record.open);
    masc_log_message( 0, "     active: %d", state->info.record.active);
    masc_log_message( 0, "avail ports: 0x%04X", state->info.record.avail_ports);
    masc_log_message( 0, "  mod ports: 0x%04X", state->info.record.mod_ports);

    return 0;
}


int32
pdanx_get( struct anx_state* state, char* key, struct mas_package* arg, struct mas_package* r_package )
{
    return 0;
}

int32
pdanx_set( struct anx_state* state, char* key, struct mas_package* arg )
{
    return 0;
}



int32
pdanx_set_mixer_volume(struct anx_state* state, int ch_id )
{
    int32 err;
    int left, right, gain, balance;
    double theta;
    struct mixer_channel* mch = &(state->mch[ch_id]); /* convenience */
    struct audio_info* info = & state->pdstate.info;

    /* normalize the possible 10dB gain */
    left = (dbvol_to_linear( mch->left )*255)/110;
    right = (dbvol_to_linear( mch->right )*255)/110;

    /* and clamp */
    if ( left < 0 ) left = 0;
    if ( left > 255 ) left = 255;
    if ( right < 0 ) right = 0;
    if ( right > 255 ) right = 255;

    if ( mch->is_stereo )
    {
    	gain = sqrt( left*left + right*right );
        
    	if ( left == 0 ) theta = M_PI / 2;

        theta = atan( sqrt( (double)right/(double)left ) );
   	balance = 2.0 * AUDIO_RIGHT_BALANCE * theta / M_PI;
    }	
    else
    {
        gain = left;
        balance = AUDIO_MID_BALANCE;
    }
    masc_log_message( 0, "gain: %d, balance: %d", gain, balance );

    if ( mch->iodir == MIX_CHANNEL_OUT )
    {
        info->play.gain = gain;
        info->play.balance = balance;
    }
    else
    {
        info->record.gain = gain;
        info->record.balance = balance;
    }
    
    err = solaris_set_info( & state->pdstate );
    if ( err < 0 ) return err;

    return 0;
}

int32
pdanx_get_mixer_volume(struct anx_state* state, int ch_id )
{
    int32 err;
    int left, right, balance;
    double theta;
    struct mixer_channel* mch = &(state->mch[ch_id]); /* convenience */
    struct audio_info* info = & state->pdstate.info;
    
    /* get the device capabilities */
    err = solaris_get_info( & state->pdstate );
    if ( err < 0 ) return err;

    if ( mch->iodir == MIX_CHANNEL_OUT )
    {
        left  = info->play.gain;
        right = info->play.gain;
        balance = info->play.balance;
    }
    else
    {
        left  = info->record.gain;
        right = info->record.gain;
        balance = info->record.balance;
    }
    
    /* assume balance works like a constant power panpot */
    theta = M_PI * balance / ( 2 * AUDIO_RIGHT_BALANCE );
    left *= cos(theta);
    right *= sin(theta);
    
    left = linear_to_dbvol( (left * 110)/255 );
    right = linear_to_dbvol( (right * 110)/255 );

    /* and clamp */
    if ( left > 60 ) left = 60;
    if ( right > 60 ) right = 60;

    mch->left = left;
    mch->right = right;

    return 0;
}

int32
pdanx_set_recording_source(struct anx_state* state, int ch_id )
{
    int32 err;

    /* set the port */
    state->pdstate.info.record.port = state->pdstate.solaris_ap_map[ch_id];

    err = solaris_set_info( & state->pdstate );
    if ( err < 0 ) return err;
    
    return 0;
}

int32
pdanx_get_recording_source(struct anx_state* state )
{
    int j;
    int32 err;
    struct mixer_channel* mch = state->mch; /* convenience */

    /* get the device capabilities */
    err = solaris_get_info( & state->pdstate );
    if ( err < 0 ) return err;
    
    for (j=0; *(mch[j].name) != 0; j++)
    {
        if (state->pdstate.info.record.port == state->pdstate.solaris_ap_map[j])
            mch[j].recsrc = TRUE;
        else
            mch[j].recsrc = FALSE;
    }

    return 0;
}


/*
 * The sample counter counts the number of N-channel samples that have
 * been successfully played by the interface. It will not increment if
 * nothing is playing, and it will not increment during buffer
 * underruns.  Its value is held in scnt->val in the master clock
 * value structure scnt.
 *
 * It is up to you to register a "veto" of the measurement if any
 * condition occurs that you believe could affect it.  In the case of
 * these OSS drivers, if the interface isn't playing or there are
 * buffer underruns, a veto is registered.  Additionally, the outer
 * layer of the anx device will veto the measurement if the sample
 * counter wraps.
 *
 * This function returns zero (0) if an error occurs, otherwise it
 * returns the sample counter's value.  This function must NOT return
 * a valid count when an error has occurred.  For the OSS drivers, we
 * check for playback underruns immediately after retrieving the
 * sample counter.
 *
 * The counter only counts at the sampling frequency, and is
 * independent of the number of channels or resolution of the samples:
 * e.g. for 44.1 kHz, 16-bit, stereo, the counter will read 44100
 * after one second, 88200 after 2 seconds, and so on.
 * 
 */

uint32
pdanx_get_sample_count( struct anx_state* state, struct mas_mc_clkval* scnt )
{
    struct audio_info	info;
    int32   return_value = 0, err;

    
    masc_entering_log_level("Estimating the frequency of the sound card: pdanx_get_sample_count()");

    /* Make sure the pointer is valid */
    if(!scnt)
    {
	return_value = 0;
	goto success;
    }

    /* Get the information from the Solaris driver */
    if((err = solaris_get_info(&state->pdstate)) < 0)
    {
	return_value = 0;
	goto failure;
    }

    /* If true then the buffer has over/underflowed and interrupted the playing of samples. */
    if(state->pdstate.info.play.error)
    {
	scnt->veto = TRUE;
	return_value = 0;

	/* Reset the error code. */
	AUDIO_INITINFO(&info);
	info.play.error = 0;
	if(ioctl(state->pdstate.fd, AUDIO_SETINFO, &info) == -1)
	{
	    masc_log_message(MAS_VERBLVL_ERROR, "Error setting the audio info.");
	    return_value = 0;
	    goto failure;
	}

    }
    else
    {
	/* The driver already divides the number of bytes by (resolution * channels). */
	scnt->val = state->pdstate.info.play.samples;
	return_value = scnt->val;
    }

    goto success;
failure:
    scnt->veto = TRUE;
    return_value = 0; /* zero is fail - can't return negative */
success:
    masc_exiting_log_level();
    return return_value;

}

/********************************************************************
  LOCAL FUNCTIONS
********************************************************************/

int32
get_device_caps( struct anx_state* state )
{
    struct solaris_audio_state*  pdst = &state->pdstate;
    struct audio_info* info;
    int i;
    int32 err;
    
    /* get the device capabilities */
    err = solaris_get_info( pdst );
    if ( err < 0 ) return err;

    /* don't loop the record poll */
    state->no_loop_record = TRUE;
    
    info = &state->pdstate.info;
    /* full duplex? */
    if ( info->hw_features & AUDIO_HWFEATURE_DUPLEX )
    {
        state->is_full_duplex = TRUE;
        masc_log_message( MAS_VERBLVL_DEBUG, "device has full-duplex capability");
    }
    else
    {
        state->is_full_duplex = FALSE;
        masc_log_message( MAS_VERBLVL_DEBUG, "device has half-duplex capability");
    }

    /* Can we do single-sample stuff? */
    state->is_sample_accurate = FALSE; /* default: no */
    masc_log_message( MAS_VERBLVL_DEBUG, "device may not be sample accurate");

    /** MIXER ************/

    if ( info->play.avail_ports & AUDIO_HEADPHONE )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports DAC");
        state->dac_ch = solaris_add_mix_channel(state, "dac", state->audio_sink, MIX_CHANNEL_OUT, AUDIO_HEADPHONE );
        info->play.port = AUDIO_HEADPHONE;
	solaris_set_info( pdst );
        pdanx_get_mixer_volume( state, state->dac_ch );
        state->mch[state->dac_ch].is_stereo = TRUE; /* default */
    }
    
    if ( info->record.avail_ports & AUDIO_LINE_IN )
    {
        int ch;
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports line-in");
        ch = solaris_add_mix_channel(state, "line in", -1, MIX_CHANNEL_IN, AUDIO_LINE_IN );
        pdanx_get_mixer_volume( state, ch );
        state->mch[state->dac_ch].is_stereo = TRUE; /* default */
    }
    
    if ( info->record.avail_ports & AUDIO_AUX1_IN )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports aux 1 in");
    }
    
    if ( info->record.avail_ports & AUDIO_AUX2_IN )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports aux 2 in");
    }
    
    if ( info->record.avail_ports & AUDIO_MICROPHONE )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports microphone");
        state->mic_ch = solaris_add_mix_channel(state, "mic", -1, MIX_CHANNEL_IN, AUDIO_MICROPHONE );
        pdanx_get_mixer_volume( state, state->mic_ch );
        state->mch[state->mic_ch].is_stereo = FALSE; /* default */
    }
    
    if ( info->record.avail_ports & AUDIO_CD )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports CD");
        state->cd_ch = solaris_add_mix_channel(state, "cd", -1, MIX_CHANNEL_IN, AUDIO_CD );
        pdanx_get_mixer_volume( state, state->cd_ch );
        state->mch[state->cd_ch].is_stereo = TRUE; /* default */
    }
    
    err = pdanx_get_recording_source( state );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "can't query recording source");
	return err;
    }

    return 0;
}

/* sets up a new mixer channel.  returns -1 on error, or the ID of the
   new channel if successful. */
int32
solaris_add_mix_channel(struct anx_state* state, char* name, int32 portnum, int iodir, uint_t solaris_ap_num)
{
    int32 i;

    /* use platform-independent mixer channel add */
    i = add_mix_channel(state->mch, name, portnum, ALLOCATED_MIX_CH );
    if ( i < 0 ) return i;
    
    state->pdstate.solaris_ap_map[i] = solaris_ap_num;
    state->mch[i].iodir = iodir;
    
    return i;
}

int32
solaris_get_info( struct solaris_audio_state* pdst )
{
    /* get the device capabilities */
    if (ioctl(pdst->fd, AUDIO_GETINFO, &pdst->info) < 0)
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "can't query device capabilities");
        return mas_error(MERR_IO);
    }

    return 0;
}

int32
solaris_set_info( struct solaris_audio_state* pdst )
{
	/* Print debug info */
	/*
	printf("Setting ioctl() with these settings:\n");
	printf("pdst->info.play.*\n");
	printf("sample_rate: %u\n", pdst->info.play.sample_rate);
	printf("channels: %u\n", pdst->info.play.channels);
	printf("precision: %u\n", pdst->info.play.precision);
	printf("encoding: %u\n", pdst->info.play.encoding);
	printf("gain: %u\n", pdst->info.play.gain);
	printf("port: %u\n", pdst->info.play.port);
	printf("avail_ports: %u\n", pdst->info.play.avail_ports);
	printf("mod_ports: %u\n", pdst->info.play.mod_ports);
	printf("_xxx: %u\n", pdst->info.play._xxx);
	printf("buffer_size: %u\n", pdst->info.play.buffer_size);
	printf("samples: %u\n", pdst->info.play.samples);
	printf("eof: %u\n", pdst->info.play.eof);
	printf("pause: %u\n", pdst->info.play.pause);
	printf("error: %u\n", pdst->info.play.error);
	printf("waiting: %u\n", pdst->info.play.waiting);
	printf("balance: %u\n", pdst->info.play.balance);
	printf("minordev: %u\n", pdst->info.play.minordev);
	printf("open: %u\n", pdst->info.play.open);
	printf("active: %u\n", pdst->info.play.active);

	printf("\npdst->info.record.*\n");
	printf("sample_rate: %u\n", pdst->info.record.sample_rate);
	printf("channels: %u\n", pdst->info.record.channels);
	printf("precision: %u\n", pdst->info.record.precision);
	printf("encoding: %u\n", pdst->info.record.encoding);
	printf("gain: %u\n", pdst->info.record.gain);
	printf("port: %u\n", pdst->info.record.port);
	printf("avail_ports: %u\n", pdst->info.record.avail_ports);
	printf("mod_ports: %u\n", pdst->info.record.mod_ports);
	printf("_xxx: %u\n", pdst->info.record._xxx);
	printf("buffer_size: %u\n", pdst->info.record.buffer_size);
	printf("samples: %u\n", pdst->info.record.samples);
	printf("eof: %u\n", pdst->info.record.eof);
	printf("pause: %u\n", pdst->info.record.pause);
	printf("error: %u\n", pdst->info.record.error);
	printf("waiting: %u\n", pdst->info.record.waiting);
	printf("balance: %u\n", pdst->info.record.balance);
	printf("minordev: %u\n", pdst->info.record.minordev);
	printf("open: %u\n", pdst->info.record.open);
	printf("active: %u\n", pdst->info.record.active);

	printf("\npdst->fd: %x\n", pdst->fd);

	printf("\npdst->info.*\n");
	printf("monitor_gain: %u\n", pdst->info.monitor_gain);
	printf("output_muted: %u\n", pdst->info.output_muted);
	printf("ref_cnt: %u\n", pdst->info.ref_cnt);
	printf("hw_features: %u\n", pdst->info.hw_features);
	printf("sw_features: %u\n", pdst->info.sw_features);
	printf("sw_features_enabled: %u\n\n", pdst->info.sw_features_enabled);
	*/

    /* get the device capabilities */
    if (ioctl(pdst->fd, AUDIO_SETINFO, &pdst->info) < 0)
    {
	/* Print debug info */
	/*
	printf("ioctl() failed with these settings:\n");
	printf("pdst->info.play.*\n");
	printf("sample_rate: %u\n", pdst->info.play.sample_rate);
	printf("channels: %u\n", pdst->info.play.channels);
	printf("precision: %u\n", pdst->info.play.precision);
	printf("encoding: %u\n", pdst->info.play.encoding);
	printf("gain: %u\n", pdst->info.play.gain);
	printf("port: %u\n", pdst->info.play.port);
	printf("avail_ports: %u\n", pdst->info.play.avail_ports);
	printf("mod_ports: %u\n", pdst->info.play.mod_ports);
	printf("_xxx: %u\n", pdst->info.play._xxx);
	printf("buffer_size: %u\n", pdst->info.play.buffer_size);
	printf("samples: %u\n", pdst->info.play.samples);
	printf("eof: %u\n", pdst->info.play.eof);
	printf("pause: %u\n", pdst->info.play.pause);
	printf("error: %u\n", pdst->info.play.error);
	printf("waiting: %u\n", pdst->info.play.waiting);
	printf("balance: %u\n", pdst->info.play.balance);
	printf("minordev: %u\n", pdst->info.play.minordev);
	printf("open: %u\n", pdst->info.play.open);
	printf("active: %u\n", pdst->info.play.active);

	printf("\npdst->info.record.*\n");
	printf("sample_rate: %u\n", pdst->info.record.sample_rate);
	printf("channels: %u\n", pdst->info.record.channels);
	printf("precision: %u\n", pdst->info.record.precision);
	printf("encoding: %u\n", pdst->info.record.encoding);
	printf("gain: %u\n", pdst->info.record.gain);
	printf("port: %u\n", pdst->info.record.port);
	printf("avail_ports: %u\n", pdst->info.record.avail_ports);
	printf("mod_ports: %u\n", pdst->info.record.mod_ports);
	printf("_xxx: %u\n", pdst->info.record._xxx);
	printf("buffer_size: %u\n", pdst->info.record.buffer_size);
	printf("samples: %u\n", pdst->info.record.samples);
	printf("eof: %u\n", pdst->info.record.eof);
	printf("pause: %u\n", pdst->info.record.pause);
	printf("error: %u\n", pdst->info.record.error);
	printf("waiting: %u\n", pdst->info.record.waiting);
	printf("balance: %u\n", pdst->info.record.balance);
	printf("minordev: %u\n", pdst->info.record.minordev);
	printf("open: %u\n", pdst->info.record.open);
	printf("active: %u\n", pdst->info.record.active);

	printf("\npdst->fd: %x\n", pdst->fd);

	printf("\npdst->info.*\n");
	printf("monitor_gain: %u\n", pdst->info.monitor_gain);
	printf("output_muted: %u\n", pdst->info.output_muted);
	printf("ref_cnt: %u\n", pdst->info.ref_cnt);
	printf("hw_features: %u\n", pdst->info.hw_features);
	printf("sw_features: %u\n", pdst->info.sw_features);
	printf("sw_features_enabled: %u\n", pdst->info.sw_features_enabled);
	*/

        masc_log_message( MAS_VERBLVL_DEBUG, "can't set device config");
	masc_logerror( mas_error(MERR_IO)|mas_make_serror(errno), "WTF?!");
        return mas_error(MERR_IO);
    }
    return 0;
}

