/*
 * 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: oss.c,v 1.5 2003/06/26 21:13:43 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.
 *
 */

/**
 ** NOTE
 **
 ** DMA does not work.  You'll be the first to know when it does.
 **
 **/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <math.h>
#include "mas/mas_dpi.h"
#include "anx_internal.h"
#include "fd_device.h"
#include "oss_profile.h"

static int _pow2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
		       4096, 8192, 16384, 32768, 65536, 131072, -1 };

/*************************************************************************
 * LOCAL PROTOTYPES
 *************************************************************************/
static int32 get_device_caps( struct anx_state* state );
static struct dmainfo* oss_mmap_dma_buffer( struct anx_state* state, int prot, int flags );
static int write_to_dma( struct dmainfo* dma, char* data, int length );
static int32 get_dma_pos( int fd, struct dmainfo* dma, int input );
static int32 oss_set_fragment_stuff( struct anx_state* state, int target_time_ms, int target_fragsize );
static void clear_dma( struct dmainfo* dma );
static int32 oss_add_mix_channel(struct anx_state* state, char* name, int32 portnum, int oss_ch_num);

/* 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 )
{
    int32                    err;

    /* null out the platform-dependent state */
    memset( &(state->pdstate), 0, sizeof state->pdstate );

    /* special case */
    state->pdstate.fd = -1;
    
    /* open the sound device */
    err = pdanx_open_resource( state );
    if ( err < 0 )
        return err;
    
    /* get device capabilities */
    err = get_device_caps( state );
    if ( err < 0 )
        return err;

    /* make the buffer */
    err = anx_make_buffer( &state->play_buffer, PLAY_BUFFER_SIZE, 0 );
    if ( err < 0 )
    {
        masc_exiting_log_level();
        return err;
    }
    
    return 0;
}

/* Open the audio device */
int32
pdanx_open_resource( struct anx_state* state )
{
    int                      holder;

    /* wait... is it already open?! bail if it is. */
    if ( state->pdstate.fd >= 0 )
        return 0;
        
    /* open audio device */
    state->pdstate.fd = open_audio_device_fd( state, "/dev/dsp" );
    if ( state->pdstate.fd < 0 )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "anx: [error] Can't open the audio device \"/dev/dsp\".");
        return mas_error(MERR_FILE_CANNOT_OPEN);
    }

    /* open the mixer */
    if (( state->pdstate.mfd = open ("/dev/mixer", O_RDONLY )) < 0 )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "anx: [warning] Can't open the audio mixer \"/dev/mixer\".");
        masc_log_message( MAS_VERBLVL_WARNING, "anx: [warning] OK.  Using audio device for mixer");
        state->pdstate.mfd = state->pdstate.fd;
    }
    
    /* reset dsp */
    holder = 0; 
    if (ioctl(state->pdstate.fd, SNDCTL_DSP_RESET, &holder) < 0)
    {
        masc_log_message( MAS_VERBLVL_ERROR, "can't reset the audio device: %s", strerror(errno));
	return mas_error(MERR_IO);
    }

    return 0;
}


/* Close the audio device */
int32
pdanx_close_resource( struct anx_state* state )
{
    close( state->pdstate.fd );
    close( state->pdstate.mfd );
    state->pdstate.fd = -1;
    state->pdstate.configured = FALSE;
    
    return 0;
}

int32
pdanx_exit_instance( struct anx_state* state, void* predicate )
{
    pdanx_close_resource( state );
    
    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 )
{
    int32 err;
    
    /* act based on prior state */
    if ( state->res_state == ACTIVE || state->res_state == ACTIVE_PENDING )
    {
        /* Switching between active states requires no action. */
        /* Switching to either inactive state requires closing the
           device. */
        if ( res_state == INACTIVE || res_state == INACTIVE_PENDING )
            pdanx_close_resource( state );
    }
    else /* inactive or inactive_pending */
    {
        /* switching between inactive states requires no action. */
        /* Switching to either active state: */

        if ( res_state == ACTIVE || res_state == ACTIVE_PENDING )
        {
            err = pdanx_open_resource( state );
            if ( err < 0 ) return err;

            /* if either of the ports are active, we have to configure
               the driver */
            if (state->source_active)
                pdanx_configure_resource( state, state->audio_source );

            if (state->sink_active)
                pdanx_configure_resource( state, state->audio_sink );
        }
    }
    
    return 0;
}

/* Given the formats specified in the anx_state: play_format,
 * play_sample_rate, play_resolution, play_channels, and rec_format,
 * rec_sample_rate, rec_resolution, and rec_channels, configure the
 * device driver.  This same function serves both playback and
 * recording. */
int32
pdanx_configure_resource( struct anx_state *state, int32 portnum )
{
    int32                    err;
    int                      oss_format = -1;
    int                      oss_stereo = -1;
    int                      oss_speed  = -1;
    int                      target_fragsize;
    struct audio_buf_info    info;
    int                      target_time_ms;

    /* Convert the stored format settings to OSS-speak */
    switch ( state->play_format )
    {
    case MAS_ULAW_FMT:
        oss_format = AFMT_MU_LAW;
        break;
    case MAS_ALAW_FMT:
        oss_format = AFMT_A_LAW;
        break;
    case MAS_LINEAR_FMT:
        if ( state->play_resolution == 16 )
        {
#ifdef MAS_LITTLE_ENDIAN
            oss_format = AFMT_S16_LE;
#else
            oss_format = AFMT_S16_BE;
#endif
        }
        else if ( state->play_resolution == 8 )
            oss_format = AFMT_S8;
        else
            return mas_error(MERR_INVALID);
        break;
    case MAS_ULINEAR_FMT:
        if ( state->play_resolution == 16 )
        {
#ifdef MAS_LITTLE_ENDIAN
            oss_format = AFMT_U16_LE;
#else
            oss_format = AFMT_U16_BE;
#endif
        }
        else if ( state->play_resolution == 8 )
            oss_format = AFMT_U8;
        else
            return mas_error(MERR_INVALID);
        break;
    default:
        return mas_error(MERR_INVALID);
    }

    oss_stereo = state->play_channels - 1;
    oss_speed = state->play_sample_rate;

    /* record and playback must be configured the same.  If one is
       already set, then don't do ioctls. */
    if ( !state->pdstate.configured )
    {
        /* set initial targets for fragment parameters */
        target_time_ms = TARGET_DMABUF_TIME_MS;
        target_fragsize = oss_speed * state->play_bpstc *
            MIN_FRAGSIZE_US * 0.000001;

        /* try setting them */
        err = oss_set_fragment_stuff( state, target_time_ms, target_fragsize );
        
        /* change the device state */
        if (ioctl(state->pdstate.fd, SNDCTL_DSP_SETFMT, &oss_format) < 0)  
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "can't set the device format: %s", strerror(errno));
            masc_exiting_log_level();
            return mas_error(MERR_IO);
        }
    
        if (ioctl(state->pdstate.fd, SNDCTL_DSP_STEREO, &oss_stereo) < 0)
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "can't set the device channels: %s", strerror(errno));
            masc_exiting_log_level();
            return mas_error(MERR_IO);
        }
    
        if ( ioctl(state->pdstate.fd, SNDCTL_DSP_SPEED, &oss_speed) < 0 )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "can't set the device sampling rate: %s", strerror(errno));
            masc_exiting_log_level();
            return mas_error(MERR_IO);
        }

        ioctl(state->pdstate.fd, SNDCTL_DSP_GETOSPACE, &info); /* just check
                                                          output space */
        state->pdstate.fragsize = info.fragsize;
        state->pdstate.fragments = info.fragstotal;
        masc_log_message( MAS_VERBLVL_DEBUG, "Allocated %d fragments of %d bytes each.", state->pdstate.fragments, state->pdstate.fragsize);
    }

    /* device is now configured */
    state->pdstate.configured = TRUE;
    
    if ( portnum == state->audio_sink )
    {
        /* reset the playback buffer */
        state->play_buffer->fill_line = state->buftime_mt * state->play_bpstc;
        anx_reset_buffer( state->play_buffer );
        state->play_buffer->filling = TRUE;
        masc_log_message( MAS_VERBLVL_DEBUG, "anx: playback filling %.1fms buffer", 1000.0 * (float) state->play_buffer->fill_line / (oss_speed * state->play_bpstc ) );
    }
    else if ( portnum == state->audio_source )
    {
        /* this is just for debugging */
        ioctl(state->pdstate.fd, SNDCTL_DSP_GETISPACE, &info);
        state->pdstate.fragsize = info.fragsize;
        state->pdstate.fragments = info.fragstotal;
        masc_log_message( MAS_VERBLVL_DEBUG, "anx: recording using %d fragments of %d bytes each.", state->pdstate.fragments, state->pdstate.fragsize);
        /******************************/
    }
    
    return 0;
}

int32
pdanx_configure_port( struct anx_state* state, int32 portnum, struct mas_data_characteristic* dc )
{
    return pdanx_configure_resource( state, portnum );
}

int32
pdanx_disconnect_port( struct anx_state* state, int32 portnum )
{
    return 0;
}

int32 
pdanx_playback_start( struct anx_state* state )
{

#if 0 /* triggering code - for use with DMA, but causes problems on
         some cards.*/
 
    /* null out the trigger */
    if ( !state->played_bytes )
    {
        /* preserve record trigger */
        state->trigger &= ~PCM_ENABLE_OUTPUT;
        ioctl( state->fd, SNDCTL_DSP_SETTRIGGER, &state->trigger );
    }
#endif /* triggering code */

    return 0;
}

int32 
pdanx_playback_stop( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_playback_pause( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_playback_poll( struct anx_state* state, struct mas_data* data )
{
    struct oss_state*        pdst = &state->pdstate;
    int                      error;
    int32                    err;
    audio_buf_info           buf_info;
    int device_buffer_used = 0;
    int report_xrun = 0;

#if 0
    {
        int remain;
        uint32 ts;
        static uint32 ts_ref = 0;
        
        ioctl( pdst->fd, SNDCTL_DSP_GETODELAY, &remain );
        masc_get_short_usec_ts( &ts );
        if ( ts_ref == 0 ) ts_ref = ts;
        
        masc_log_message( 0, "pdanx_playback_poll: output buffer [us bytes]: %u %d", ts-ts_ref, remain);
    }
#endif

    if ( pdst->is_mmap_able )
    {
        int trigger = FALSE;
        
        /* set trigger this round */
        if ( !state->played_bytes ) trigger = TRUE;

        get_dma_pos( pdst->fd, pdst->dmaplay, FALSE );
        write_to_dma( pdst->dmaplay, data->segment, data->length );

        
        /* enable playback */
        if (trigger)
        {
            /* preserve record trigger */
            pdst->trigger |= PCM_ENABLE_OUTPUT;
            ioctl( pdst->fd, SNDCTL_DSP_SETTRIGGER, &pdst->trigger );
        }
        state->played_bytes += data->length;        
        if ( pdst->dmaplay->bytes > state->played_bytes )
            return mas_error( MERR_XRUN );
        
    }
    else
    {
        if ( !state->play_buffer->filling )
        {
            /* check the buffer space used in the device */
            if ( ioctl(pdst->fd, SNDCTL_DSP_GETOSPACE, &buf_info) == 0 )
                device_buffer_used = (buf_info.fragstotal * buf_info.fragsize) - buf_info.bytes;
            else device_buffer_used = 0;

#if 0
            masc_log_message( 0, "device_buffer_used: %d", device_buffer_used );
#endif
            
#ifdef SNDCTL_DSP_GETERROR  /* we have xrun detection */
            /* test for underruns */
            if ( ioctl( pdst->fd, SNDCTL_DSP_GETERROR, &pdst->errinfo) < 0)
                return mas_error(MERR_IO);
        
            /* report an XRUN if detected, and set to filling */
            if (pdst->errinfo.play_underruns)
                report_xrun = TRUE;
            
#endif /* SNDCTL_DSP_GETERROR */
        
            /* if the device buffer has underrun, start filling it */
            if ( device_buffer_used == 0 )
                report_xrun = TRUE;
        }

        if ( state->rebuffer )
        {
            state->play_buffer->filling = TRUE;
            state->rebuffer = FALSE;
        }

        /** 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, "anx: filling buffer - %%%d percent full", 100 * state->play_buffer->pos/state->play_buffer->fill_line );
            if ( err < 0) return err;
        }

        if (! state->play_buffer->filling ) /* catches changes from above */
        {
            /* Write anything in the buffer, all at once. */
            if (state->play_buffer->pos > 0)
            {
                /* set reference mark */
                state->mcref = state->mcnow;
                state->mtref = data->header.media_timestamp - ( state->play_buffer->pos - data->length ) / state->play_bpstc;
                state->valid_refmark = TRUE;
                
                /* write the buffer */
                if (( error = write (pdst->fd, state->play_buffer->contents, state->play_buffer->pos)) != state->play_buffer->pos )		
                    return mas_error(MERR_IO);
        
                /* enable playback */
                if (!state->played_bytes)
                {
#if 0 /* trigger code causes problems with some cards ... working on
       * it */
                    /* preserve record trigger */
                    state->trigger |= PCM_ENABLE_OUTPUT;
                    ioctl( pdst->fd, SNDCTL_DSP_SETTRIGGER, &state->trigger );
#endif
                }
                
                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;        
            }
        }
    }

    /* we ran out of buffer */
    if ( report_xrun ) return mas_error(MERR_XRUN);

    return 0;
}

int32 
pdanx_record_start( struct anx_state* state )
{
    struct oss_state*  pdst = &state->pdstate;

#if 0 /* triggering code not working, for now */
    /* trigger */
    if ( !state->rec_bytes )
    {
        /* preserve play trigger, set record low */
        state->trigger &= ~PCM_ENABLE_INPUT;
        ioctl( state->fd, SNDCTL_DSP_SETTRIGGER, &state->trigger );

        /* set trigger high */
        state->trigger |= PCM_ENABLE_INPUT;
        ioctl( state->fd, SNDCTL_DSP_SETTRIGGER, &state->trigger );
    }
#endif

    /* If we were recording before, stopped, and have now restarted,
       there's gonna be a ton of junk in the buffer.  read it all and
       trash it! */
    if ( state->rec_bytes )
    {
        audio_buf_info recinfo;
        void* trash;

        ioctl(pdst->fd, SNDCTL_DSP_GETISPACE, &recinfo);

        if ( recinfo.fragsize*recinfo.fragments )
        {
            trash = masc_rtalloc(recinfo.fragsize*recinfo.fragments);
            if ( read (pdst->fd, trash, recinfo.fragsize*recinfo.fragments) != recinfo.fragsize*recinfo.fragments )
            {
                masc_rtfree( trash ); /* junk it */
                return mas_error(MERR_IO) | mas_make_serror( errno );
            }
            masc_rtfree( trash ); /* junk it */
        }
        else
        {
             /* On some devies, the ioctl above will return bogus
              * information until the overruns are cleared.  So, don't
              * believe it! Read the entire buffer and scrap it.  */
            trash = masc_rtalloc(pdst->fragsize*pdst->fragments);
            if ( read (pdst->fd, trash, pdst->fragsize*pdst->fragments) != pdst->fragsize*pdst->fragments )
            {
                masc_rtfree( trash ); /* junk it */
                return mas_error(MERR_IO) | mas_make_serror( errno );
            }
            masc_rtfree( trash ); /* junk it */
        }
        
    }

    return 0;
}

int32 
pdanx_record_stop( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_record_pause( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_record_poll( struct anx_state* state, struct mas_data** data_ptr )
{
    struct oss_state*  pdst = &state->pdstate;
    audio_buf_info recinfo;
    struct mas_data* data;

    *data_ptr = 0;

    /* can we read anything?  avoids blocking - could just use
     * non-blocking I/O, dummy. */
    if ( ioctl(pdst->fd, SNDCTL_DSP_GETISPACE, &recinfo) <  0 )
        return mas_error(MERR_IO);

    /* 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 ( (recinfo.fragsize*recinfo.fragments >= MAS_ANX_SEGLEN) ||
            state->rec_state == START_STATE)
    {
        data = MAS_NEW( data );
        masc_setup_data( data, MAS_ANX_SEGLEN );
        
        if ( read (pdst->fd, data->segment, MAS_ANX_SEGLEN) != MAS_ANX_SEGLEN )
        {
            masc_strike_data( data );
            masc_rtfree( data );
            return mas_error(MERR_IO) | mas_make_serror( errno );
        }

        *data_ptr = data;
    }

    return 0;
}

int32
pdanx_show_state( struct anx_state* state )
{
    u_int32                  holder;
    audio_buf_info           playinfo;

    masc_log_message(0, "*-- platform dependent anx state -------------------------------\n");

    ioctl(state->pdstate.fd, SNDCTL_DSP_GETBLKSIZE, &holder);
    masc_log_message(0, "fragment size: %d", holder);

    memset( &playinfo, 0, sizeof playinfo );
    ioctl(state->pdstate.fd, SNDCTL_DSP_GETOSPACE, &playinfo);

    masc_log_message(0, "    fragments: %d", playinfo.fragments);
    masc_log_message(0, "   fragstotal: %d", playinfo.fragstotal);
    masc_log_message(0, "     fragsize: %d", playinfo.fragsize);
    masc_log_message(0, "        bytes: %d", playinfo.bytes);

    masc_log_message(0, "for recording");
    ioctl(state->pdstate.fd, SNDCTL_DSP_GETISPACE, &playinfo);

    masc_log_message(0, "    fragments: %d", playinfo.fragments);
    masc_log_message(0, "   fragstotal: %d", playinfo.fragstotal);
    masc_log_message(0, "     fragsize: %d", playinfo.fragsize);
    masc_log_message(0, "        bytes: %d", playinfo.bytes);

    return 0;
}


int32
pdanx_get( struct anx_state* state, char* key, struct mas_package* arg, struct mas_package* r_package )
{
    struct oss_state*  pdst = &state->pdstate;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "list", "gain_db", "gain_linear", "channels", "recsource",
          "outremain", "playclock", "" };
    int remain;
    int i, n=0;
    struct count_info cinfo;
    
    /* count the defined nuggets */
    while ( *nuggets[n] != 0 ) n++;

    i = masc_get_string_index(key, nuggets, n);

    switch(i)
    {
    case 5: /*outremain*/
        if ( ioctl( pdst->fd, SNDCTL_DSP_GETODELAY, &remain ) < 0 )
            masc_pushk_int32( r_package, "error", mas_error( MERR_IO ) );
        else
            masc_pushk_int32( r_package, "outremain", remain );
        break;
    case 6: /*playclock*/
        if ( ioctl( pdst->fd, SNDCTL_DSP_GETOPTR, &cinfo ) < 0 )
            masc_pushk_int32( r_package, "error", mas_error( MERR_IO ) );
        else
        {
            masc_pushk_int32( r_package, "ticks", cinfo.bytes );
        }
        
        break;
    default:
        return mas_error(MERR_INVALID);
        break;
    }

    return 0;
}

int32
pdanx_set( struct anx_state* state, char* key, struct mas_package* arg )
{
    /* everything taken care of in platform independent code */
    return 0;
}

int32
pdanx_set_mixer_volume(struct anx_state* state, int ch_id )
{
    int32 holder;
    int left, right;
    struct mixer_channel* mch = &(state->mch[ch_id]); /* convenience */

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

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

    /* ioctl expects 0x0000RRLL - (RR) is the right channel, (LL) is
       the left.  Each ranges 0-100 */
    holder = (left & 0xFF) + ( ( right & 0xFF ) << 8 );
    
    if (ioctl(state->pdstate.mfd, MIXER_WRITE(state->pdstate.oss_mch_map[ch_id]), &holder) < 0)
        return mas_error(MERR_IO);

    return 0;
}

int32
pdanx_get_mixer_volume(struct anx_state* state, int ch_id )
{
    int32 holder;
    int left, right;
    struct mixer_channel* mch = &(state->mch[ch_id]); /* convenience */

    if (ioctl(state->pdstate.mfd, MIXER_READ(state->pdstate.oss_mch_map[ch_id]), &holder) < 0)
        return mas_error(MERR_IO);

    /* The ioctl sets holder to 0x0000RRLL. (RR) is the right channel,
       (LL) is the left.  Each ranges 0-100 */
    left = holder & 0xFF;
    right = (holder >> 8) & 0xFF;
    
    left = linear_to_dbvol( (left * 11)/10 );
    right = linear_to_dbvol( (right * 11)/10 );

    /* 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 holder = 0;

    /* ioctl expects 0x0000RRLL - (RR) is the right channel, (LL) is
       the left.  Each ranges 0-100 */
    holder = 1 << state->pdstate.oss_mch_map[ch_id];
    
    if (ioctl(state->pdstate.mfd, SOUND_MIXER_WRITE_RECSRC, &holder) < 0)
        return mas_error(MERR_IO);

    return 0;
}

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

    if (ioctl(state->pdstate.mfd, SOUND_MIXER_READ_RECSRC, &holder) < 0)
	return mas_error(MERR_IO);

    for (j=0; *(mch[j].name) != 0; j++)
    {
        if ( holder & ( 1 << state->pdstate.oss_mch_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 count_info cinfo;
    audio_buf_info    buf_info;
    int device_buffer_used;

    if ( scnt == 0 ) return 0;

    /* grab the byte counter */
    if ( ioctl( state->pdstate.fd, SNDCTL_DSP_GETOPTR, &cinfo ) < 0 )
        goto fail;

    /* check the buffer space used in the device */
    if ( ioctl(state->pdstate.fd, SNDCTL_DSP_GETOSPACE, &buf_info) == 0 )
        device_buffer_used = (buf_info.fragstotal * buf_info.fragsize) - buf_info.bytes;
    else device_buffer_used = 0;

    /* Fail if the device buffer has underrun! */
    if ( device_buffer_used == 0 )
        goto fail;

    /* Otherwise, counter is valid; set the MC clock value to the
     * sample count. */
    scnt->val = cinfo.bytes / state->play_bpstc;

    return scnt->val;

 fail:
    scnt->veto = TRUE;

    return 0;
    
}

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

int32
get_device_caps( struct anx_state* state )
{
    struct oss_state*  pdst = &state->pdstate;
    int holder = 0;
    int sm = 0;
    int i;
    int32 err;
    
    /* get the device capabilities */
    if (ioctl(pdst->fd, SNDCTL_DSP_GETCAPS, &holder) < 0)
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "can't query device capabilities: %s", strerror(errno));
	return mas_error(MERR_IO);
    }

    /* full duplex? */
    if ( holder & DSP_CAP_DUPLEX )
    {
        state->is_full_duplex = 1;
        masc_log_message( MAS_VERBLVL_DEBUG, "device has full-duplex capability");
    }
    else
    {
        state->is_full_duplex = 0;
        masc_log_message( MAS_VERBLVL_DEBUG, "device has half-duplex capability");
    }

    /* Can we do single-sample stuff? */
    if ( holder & DSP_CAP_REALTIME )
        state->is_sample_accurate = 1;
    else
        state->is_sample_accurate = 0;

    /* or, is there a big buffer on the card? */
    if ( holder & DSP_CAP_BATCH )
    {
        pdst->is_fragment_accurate = 0;
        masc_log_message( MAS_VERBLVL_DEBUG, "device isn't fragment accurate");
    }
    else
    {
        pdst->is_fragment_accurate = 1;
        if ( state->is_sample_accurate )
            masc_log_message( MAS_VERBLVL_DEBUG, "device is sample accurate");
        else masc_log_message( MAS_VERBLVL_DEBUG, "device is fragment accurate");
    }

    /* could we MMAP the DMA buffer?  (not used) */
    if ( holder & DSP_CAP_MMAP )
    {
        pdst->is_mmap_able = 1;
        masc_log_message( MAS_VERBLVL_DEBUG, "device has mmap-able buffer");
    }
    else
    {
        pdst->is_mmap_able = 0;
        masc_log_message( MAS_VERBLVL_DEBUG, "device has mmap-able buffer");
    }

    /***
     *** BUT SHUT MMAP OFF FOR NOW
     ***/
    pdst->is_mmap_able = 0;
    
    /** MIXER ************/
    /* get the mask of available devices */
    if (ioctl(pdst->mfd, SOUND_MIXER_READ_DEVMASK, &holder) < 0)
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "can't query mixer capabilities: %s", strerror(errno));
	return mas_error(MERR_IO);
    }

    /* get the mask of stereo devices */
    if (ioctl(pdst->mfd, SOUND_MIXER_READ_STEREODEVS, &sm) < 0)
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "can't query mixer capabilities: %s", strerror(errno));
	return mas_error(MERR_IO);
    }

    if ( holder & (1 << SOUND_MIXER_VOLUME) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports main");
        state->main_mix_ch = oss_add_mix_channel(state, "main mix", -1, SOUND_MIXER_VOLUME );
        pdanx_get_mixer_volume( state, state->main_mix_ch );
    }
    
    if ( holder & (1 << SOUND_MIXER_TREBLE) )
    {
        if ( holder & (1 << SOUND_MIXER_BASS) )
            masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports treble and bass adjustments");
    }
    
    if ( holder & (1 << SOUND_MIXER_PCM) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports DAC");
        state->dac_ch = oss_add_mix_channel(state, "dac", state->audio_sink, SOUND_MIXER_PCM );
        pdanx_get_mixer_volume( state, state->dac_ch );
    }
    
    if ( holder & (1 << SOUND_MIXER_SPEAKER) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports PC speaker");
    }
    
    if ( holder & (1 << SOUND_MIXER_LINE) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports line-in");
    }
    
    if ( holder & (1 << SOUND_MIXER_LINE1) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports line 1 in");
    }
    
    if ( holder & (1 << SOUND_MIXER_LINE2) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports line 2 in");
    }
    
    if ( holder & (1 << SOUND_MIXER_LINE3) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports line 3 in");
    }
    
    if ( holder & (1 << SOUND_MIXER_MIC) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports microphone");
        state->mic_ch = oss_add_mix_channel(state, "mic", -1, SOUND_MIXER_MIC );
        pdanx_get_mixer_volume( state, state->mic_ch );
    }
    
    if ( holder & (1 << SOUND_MIXER_CD) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports CD");
        state->cd_ch = oss_add_mix_channel(state, "cd", -1, SOUND_MIXER_CD );
        pdanx_get_mixer_volume( state, state->cd_ch );
    }
    
    if ( holder & (1 << SOUND_MIXER_RECLEV) )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "mixer supports recording level");
    }

    err = pdanx_get_recording_source( state );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "can't query recording source");
	return err;
    }

    /* Walk through the mixer channels and test the stereo mask (from
       the prior ioctl() to see if each is stereo */
    for (i=0; *(state->mch[i].name) != 0; i++)
    {
        if ( sm & (1 << pdst->oss_mch_map[i]) )
            state->mch[i].is_stereo = TRUE;
    }
    
    return 0;
}


struct dmainfo*
oss_mmap_dma_buffer( struct anx_state* state, int prot, int flags )
{
    struct oss_state*  pdst = &state->pdstate; 
    int bail = FALSE;
    int failed = FALSE;
    int refrag = FALSE;
    struct dmainfo* dma;
    int target_ms;

    dma = MAS_NEW(dma);
    if (dma == 0) return 0;
    
    /* these HAVE to be equivalent */
    dma->size = pdst->fragsize * pdst->fragments;

    while ( ! bail )
    {
        target_ms = 1000 * dma->size / (state->play_sample_rate * state->play_bpstc);

        if ( refrag )
        {
            /* this will NOT work */
            masc_rtfree( dma );
            return 0;

            oss_set_fragment_stuff( state, target_ms, pdst->fragsize+1 );
            refrag = FALSE;

            /* these HAVE to be equivalent */
            dma->size = pdst->fragsize * pdst->fragments;

            /* recompute */
            target_ms = 1000 * dma->size / (state->play_sample_rate * state->play_bpstc);
        }

            /* just for debugging */
        masc_log_message( MAS_VERBLVL_DEBUG, "Requesting mmap() of audio DMA buffer: %.2f ms", 1000.0 * (float)dma->size/((float)state->play_sample_rate * state->play_bpstc)  );
            
        /* try to mmap */
        dma->buf = (char*)mmap( 0, dma->size, prot, flags, pdst->fd, 0 );
            
        if ( dma->buf == MAP_FAILED || dma->buf == 0 )
        {
            /* if it failed with invalid argument, try halving the
             * requested size. */
            if ( errno == EINVAL )
            {
                refrag = TRUE;
                dma->size >>= 1;
            }
            else
            {
                /* we can't recover */
                bail = TRUE;
                failed = TRUE;
            }
            if ( target_ms < MIN_DMABUF_TIME_MS )
            {
                /* The DMA buffer is too small to be safe.  Give up. */
                bail = TRUE;
                failed = TRUE;
            }
        }
        else
        {
            bail = TRUE;
            dma->last = dma->buf + dma->size - 1;
            dma->ptr = dma->buf;
        }
    }
    
    if ( failed )
    {
        masc_rtfree( dma );
        return 0;
    }
    else return dma;
}

int32
write_to_dma( struct dmainfo* dma, char* data, int length )
{
    int bytes;
    int pos = 0;

    while (pos < length )
    {
        bytes = min(dma->last - dma->ptr + 1, length - pos);
        memcpy( dma->ptr, data + pos, bytes );
        dma->ptr += bytes;
        pos += bytes;
        if ( dma->ptr > dma->last)
            dma->ptr = dma->buf;
    }
    
    return 0;
}


int32
read_from_dma( struct dmainfo* dma, char* data, int length )
{
#if 0 /* not working */
    int remain;
    int bytes;

    if ( dma->dmaptr > dma->ptr )
    {
        if ( dma->dmaptr - dma->ptr >= length )
        {
            memcpy( data, dma->ptr, length );
            dma->ptr += length;
        }
        else return mas_error(MERR_COMM);
    }
    else /* dmaptr has wrapped, use different math */
    {
        /* bytes between two pointers is: bytes remaining between ptr
         * and end, plus bytes between beginning of buf and dmaptr */
        remain =  dma->last - dma->ptr + 1; /* bytes remaining */
        if ( remain + ( dma->dmaptr - dma->buf ) >= length )
        {
            bytes = min(remain, length - pos);
            memcpy( data, dma->ptr, remain );
            memcpy( data+remain, dma->buf, dma->dmaptr - dma->buf );
        }
        else return mas_error(MERR_COMM);
    }
    
    if ( dma->ptr > dma->last)
        dma->ptr = dma->buf;
#endif
    
    return 0;
}


void
clear_dma( struct dmainfo* dma )
{
    memset( dma->buf, 0, dma->size );
}

int32
get_dma_pos( int fd, struct dmainfo* dma, int input )
{
    struct count_info c;
    
    if ( input )
    {
        if ( ioctl( fd, SNDCTL_DSP_GETIPTR, &c ) < 0 )
            return mas_error(MERR_IO);
    }
    else
    {
        if ( ioctl( fd, SNDCTL_DSP_GETOPTR, &c ) < 0 )
            return mas_error(MERR_IO);
    }
    

    dma->dmaptr = dma->buf + c.ptr;
    dma->bytes = c.bytes;

    return 0;
}

int32
oss_set_fragment_stuff( struct anx_state* state, int target_time_ms, int target_fragsize )
{
    struct oss_state* pdst = &state->pdstate; 
    int oss_fragmess;
    int fragsize_pow2;
    int bail, fail;
    
    /*** COMPUTE FRAGMENT SIZE AND NUMBER OF FRAGMENTS ***************/

    /* compute log_2(target_fragsize) */
    fragsize_pow2 = 0;
    while (( _pow2[fragsize_pow2] != -1 ) && ( _pow2[fragsize_pow2] < target_fragsize ) )
        fragsize_pow2++;

    /* fragment size of 2^(i-1) BYTES */
    /* this shoots under the target fragment size in milliseconds */
    fragsize_pow2--;

    bail = FALSE;
    fail = FALSE;
    /* divine how small a fragment size we can get */
    while ( ! bail )
    {
        pdst->fragsize = _pow2[fragsize_pow2];
        pdst->fragments = target_time_ms * state->play_sample_rate * state->play_bpstc * 0.001 / pdst->fragsize;
            
        /** to set fragment size and number of fragments, form a
            32-bit integer as follows 0xMMMMSSSS, where MMMM is
            the number of fragments (buffer size) to keep in the
            kernel driver, and SSSS is used to set the size of
            each fragment as 2^(SSSS) bytes -- this is a horrible
            kluge, but at least I'm not responsible for THIS
            one. */
            
        oss_fragmess = fragsize_pow2 | ( pdst->fragments << 16 );  
            
        if ( ioctl(pdst->fd, SNDCTL_DSP_SETFRAGMENT, &oss_fragmess) < 0)
        {
            fragsize_pow2++;
            if ( _pow2[fragsize_pow2] >= ( state->play_sample_rate * state->play_bpstc * MAX_FRAGSIZE_MS * 0.001 ) )
            {
                bail = TRUE;
                fail = TRUE;
            }
        }
        else
        {
            bail = TRUE;
        }
            
    }

    if ( fail )
        masc_log_message( MAS_VERBLVL_DEBUG, "Can't set the fragment size.  I tried lots of sizes.  Sorry.  Using defaults.");
    
    return 0;
}

/* sets up a new mixer channel.  returns -1 on error, or the ID of the
   new channel if successful. */
int32
oss_add_mix_channel(struct anx_state* state, char* name, int32 portnum, int oss_ch_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.oss_mch_map[i] = oss_ch_num;
    
    return i;
}
