/*
 * 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_mix_device.c,v 1.6 2003/10/07 14:27:47 silvio Exp $ */

/*  2 OCT 2002 - rocko - verified reentrant
 *  2 OCT 2002 - rocko - NOT timestamp clean
 * 11 OCT 2002 - silvio - timestamp clean
 */

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


#define HALF_UINT32_MAX  2147483647U
#define clamp16(a) (((a) > 0)? min((a), INT16_MAX) : max((a), INT16_MIN))

/* SHOULD BE A POWER OF 2
   why? We make an implicit assumption doing arithmetics with uint32
   that they wrap at 2**32, i.e. there is an imaginary "%4294967296" there.
   Surely, if we choose CIRCBUF_SIZE such that it is an integer divisor
   of 2**32, then any subsequent explicit "% CIRCBUF_SIZE" will yield
   the correct result.
*/
#define CIRCBUF_SIZE 262144  /* 2^18 */
#define MAX_IDLE_TIME_MS 2000
/* The Solaris audio system has a bug where it must write buffers of data larger than 1K
 * in order to generate an accurate number of samples that have been played. */
#ifdef sun
# define DEFAULT_OUT_PACKET_SIZE 1152
#else
# define DEFAULT_OUT_PACKET_SIZE 576
#endif
#define MIX_GAP_MS 10
#define INITIAL_N_SINKS 16   /* doesn't really matter */
#define DYNPORT_POOL_SIZE 8     /* Keep at most 8 extra dynamic ports, just in case. */

#define MAX_VOLUME_MULTIPLIER 128  /* this will give us 128 volume levels (linear for now)  */
#define MAX_VOL_SHIFT 7            /* 2**7 = 128 */
#define DB_CUTOFF -40              /* below this ->set multiplier to 0 */
#define DO_SOFT_LIMITING 1
#define DEFAULT_THRESHOLD -2.0
#define DO_DITHER 1
#define DITHER_TBL_SIZE 44100 /* in noise_int_t's; this is half a second at stereo 44.1kHz */

/* FILE *fp; */


/* These will influence what the mas_mix_poll action does... */
#define NEED_MARK 1            /* signals that the next outgoing packet will need a mark bit */
#define ALL_SINKS_SLEEPING 2   /* signals that all the sinks are way behind; we'll stop sending out
                                * packets full of zeroes if so.*/

/* ...so will these. The need to 'reschedule' arises when someone from
   the outside (through mas_get) tells us to synchronize with a
   different clock, or to change our outgoing packet size */
#define RESCHEDULE_NONE   0
#define RESCHEDULE_CLOCK  1 /* different clock */
#define RESCHEDULE_PERIOD 2 /* different packet size means different polling period */

#define UNSPECIFIED -1


typedef int32 buffer_t;

struct circular_buffer
{
    buffer_t buf[CIRCBUF_SIZE];
    uint32 read_head; /* start of unused data */
};

struct limiter
{
    double db_threshold;
    int32 threshold;
    int32 xmax, ymax;
    int32 *tbl;
    int32 shift;
};

typedef int16 noise_int_t; /* this has to change if we want 8 bit mixer
                            * output, but for 20->16 bit it's fine and small in memory */
struct dither 
{
    int32 tbl_size;
    int32 pos;
    noise_int_t *tbl; 
};

struct sink_info
{
    uint8   bits_per_sample_and_channel; /* long name for 'resolution' since I can never remember what's meant */
    uint8   container_size_bytes; /* size in bytes of the container type of our samples */
    uint8   format;
    uint8   channels;
    uint32  reference;  /* sink reference point */
    uint32  write_head;  
    uint32  samples_in;
    int32   portnum;
    int16   multiplier;

    void (*circular_buffer_mix)( struct circular_buffer*, uint32, char*, int*, int16 );

#ifdef DEBUG
    int headstate;
#endif

};
    

/************************************************************************
 * mix_state
 *
 * State memory structure for this device instance.
 *
 ************************************************************************/
struct mix_state
{
    struct   masd_dynport_pool dp_pool;
    uint32   media_ts, sequence, srate;
    uint8    channels;                    /* of the source */
    uint8    bits_per_sample_and_channel; /* of source */
    
    int      n_sinks, out_packet_size, config_state;
    int      n_sinks_allocated;
    int      incr;
    int32    sink, source, reaction;
    uint8    reschedule;
    uint32   new_out_packet_size;
    int32    poll_clock_id;
    uint32   exact_last_poll_time;
    uint32   max_idle_time_ms;
    int8     all_sinks_late_msg_sent;
    int8     do_soft_limiting;
    int8     do_dither;
    
    struct sink_info **sinks; /* will do an array here; lookups will be
                               * faster than linked list traversals */    

    int16 mix_gap; /* this offset adds a bit of latency to be able to
    withstand scheduling jitter. The offset is added to the sink's reference
    point (or the write head if you will), not subtracted from the read head.
    That's just a matter of taste. */
    
    struct circular_buffer cb;
    struct limiter limiter;
    struct dither dither;

    void (*fill_segment)( struct mix_state*, int16* );
    
#ifdef DEBUG
    uint32 prev_rh;
    uint32 prev_wh;
#endif

};


/**** local prototypes ****************************************************/
static int  find_sink    ( struct mix_state *s, int32 portnum );
static void mix_if_needed( struct mix_state *s, struct mas_data *data, int current_sink, uint32 write_pos );

static void fill_segment_soft_nod( struct mix_state *state, int16 *buf );
static void fill_segment_soft_d  ( struct mix_state *state, int16 *buf );
static void fill_segment_hard_nod( struct mix_state *state, int16 *buf );
static void choose_fill_func( struct mix_state *state );

static void circular_buffer_mix_int8 ( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_uint8( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_int16( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_int20( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );

static void circular_buffer_mix_int16_mtos( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_int16_stom( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_int8_mtos ( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_int8_stom ( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_uint8_mtos( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_uint8_stom( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_int20_mtos( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void circular_buffer_mix_int20_stom( struct circular_buffer *cb, uint32 write_head, char *segment, int *length, int16 multiplier );
static void choose_mix_func( struct mix_state *s, int sink );

static void init_limiter( struct mix_state *s, double db_threshold, int32 max_sample_value );
static void exit_limiter( struct mix_state *s );
/* static int32 limit_sample( struct limiter *c, int32 s ); /\* unused *\/ */

static void init_dither( struct mix_state *s );
static void exit_dither( struct mix_state *s );

static int16 get_masdb_from_multiplier( int16 multiplier );


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

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


/* device specific actions *********************************************/
int32 mas_mix_print            ( int32 , void* );
int32 mas_mix_set_portname     ( int32 device_instance, void* predicate );

int32 mas_get                  ( int32 device_instance, void* predicate );
int32 mas_set                  ( int32 device_instance, void* predicate );

int32 mas_mix_mix              ( int32 device_instance, void* predicate );
int32 mas_mix_poll             ( int32 device_instance, void* predicate );



/************************************************************************/





/***************************************************************************
 * 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 mix_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, "default_mix_sink",
			   &state->sink );

    masd_get_port_by_name( device_instance, "mix_source",
			   &state->source );

    masd_get_port_by_name( device_instance, "reaction",
			   &state->reaction );

    masd_init_dynport_pool( &state->dp_pool, device_instance, state->reaction, DYNPORT_POOL_SIZE );
    
/*     fp = fopen( "/home/silvio/out.raw", "w" ); */

    state->sinks = masc_rtalloc( INITIAL_N_SINKS*sizeof(struct sink_info*) );
    if( !state->sinks )
        return mas_error(MERR_MEMORY);    
    state->n_sinks_allocated = INITIAL_N_SINKS;
    state->n_sinks = 0;
    state->incr = 0;
    
    state->reschedule       = RESCHEDULE_NONE;
    state->do_soft_limiting = DO_SOFT_LIMITING;
    state->do_dither        = DO_DITHER;
    
    state->poll_clock_id    = UNSPECIFIED;
    state->bits_per_sample_and_channel = 16;

    /* initialize circular buffer */    
    memset( state->cb.buf, 0, CIRCBUF_SIZE*sizeof(buffer_t) );
    state->cb.read_head = 0;

    state->mix_gap = UNSPECIFIED;
    state->max_idle_time_ms = MAX_IDLE_TIME_MS;
    
    state->config_state = NEED_MARK | ALL_SINKS_SLEEPING;
    
    if( state->do_soft_limiting )
        init_limiter( state, DEFAULT_THRESHOLD, (1<<(19+MAX_VOL_SHIFT))-1 );

    if( state->do_dither )
        init_dither( state );

    choose_fill_func( state );
    
/*     /\* if you'd like to print the limiter curve... *\/ */
/*     { */
/*         int i; */
/*         struct limiter *l = &state->limiter; */
/*         for( i=0/\* 3*l->threshold/4 *\/; i<1.25*l->xmax; i+=10000 ) */
/*             printf( "%d %d\n", i, limit_sample(l, i) ); */
/*     } */
    
    return 0;
}


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

int32
mas_dev_exit_instance( int32 device_instance, void* predicate )
{
    int i;
    struct mix_state*  state;
    
    masd_get_state(device_instance, (void**)&state);
    
    for( i=0; i<state->n_sinks; i++ )
    {
        masc_rtfree( state->sinks[i] );
    }
    masc_rtfree( state->sinks );

    masd_cleanup_dynport_pool( &state->dp_pool, device_instance, state->reaction );

    exit_limiter( state );
    exit_dither ( state );
    
    masc_rtfree( state );
    
    return 0;
}


/***************************************************************************
 * mas_dev_configure_port
 *
 *  predicate: int32 portnum
 *
 *  Configures a port for a certain data characteristic.  The
 *  assembler has set up the data characteristic on the port structure
 *  prior to this action.  The predicate contains only the port number.
 *  
 * returns: 0 on success
 *
 ***************************************************************************/
int32
mas_dev_configure_port( int32 device_instance, void* predicate )
{
    struct mix_state                 *state;
    struct mas_data_characteristic   *dc;
    int32                            *dataflow_port_dependency;
    int32                             err;
    int                               fi, ri, ci, sri, sr, ch, i, res;
    char                              s[20];
    int32                             portnum = *(int32*)predicate;
    int32                             new_default_portnum;
    int32                            *predicate_portnum;
    struct mas_characteristic_matrix *cmatrix;


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

    err = masd_get_data_characteristic( portnum, &dc );
    if ( err < 0 ) return err;
    
    /* retrieve the key indices from the dc */
    fi = masc_get_index_of_key(dc, "format");
    if ( fi < 0 ) return mas_error(MERR_INVALID);
    ri = masc_get_index_of_key(dc, "resolution");
    if ( ri < 0 ) return mas_error(MERR_INVALID);
    ci = masc_get_index_of_key(dc, "channels");
    if ( ci < 0 ) return mas_error(MERR_INVALID);
    sri = masc_get_index_of_key(dc, "sampling rate");
    if ( sri < 0 ) return mas_error(MERR_INVALID);
    
    sr  = atoi( dc->values[ sri ] );
    ch  = atoi( dc->values[ ci  ] );
    res = atoi( dc->values[ ri  ] );

    if( state->srate==0 )
    {
        state->srate = sr;
        /* this 'if' is to avoid re-setting when mas_set has been called for mix_gap */
        if( state->mix_gap == UNSPECIFIED )
        {
            state->mix_gap = (MIX_GAP_MS * state->srate) / 1000;
        }
    }
    else
    {
        if( sr != state->srate )
            return mas_error( MERR_INVALID );
    }


    
    /* the source is different from all the rest */
    if( portnum==state->source )
    {
        uint32 period_us;

        state->channels = ch;
        state->out_packet_size = DEFAULT_OUT_PACKET_SIZE / 2 / state->channels;

        if ( state->poll_clock_id == UNSPECIFIED )
        {
            state->poll_clock_id = masd_mc_match_rate( (int)state->srate );
        }

        if ( state->poll_clock_id >= 0 )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "mix: syncing mixer with clock %d.", state->poll_clock_id );
            masd_reaction_queue_periodic( state->reaction, device_instance,
                                          "mas_mix_poll", 0, 0, 0,
                                          state->out_packet_size,
                                          state->poll_clock_id );
        }
        else
        {
            /* there's no clock with that rate */
            
            /* this is going to introduce a roundoff error that
               accumulates with time */
            period_us = ( 1000000 * state->out_packet_size ) / state->srate;

            masc_log_message( MAS_VERBLVL_DEBUG, "mix: there's no clock with rate %d; scheduling %ul periodic action and hoping for best.", state->srate, period_us );

            masd_reaction_queue_action( state->reaction,
                                        device_instance,
                                        "mas_mix_poll", 0, 0, 0, 0, 1,
                                        MAS_PRIORITY_ASAP,
                                        period_us, 0, 0 );
        }
        return 0;
    }

    
    /* so we are configuring a sink... */

    /* set the portname to something other than default_mix_sink, but
     * do not recycle once used names (a mixer gui may look weird) */

    sprintf(s,"sink%d",state->incr++);

    /* do we need to make more space? */
    if( state->n_sinks==state->n_sinks_allocated )
    {
        struct sink_info **tmp;
        state->n_sinks_allocated *= 2; /* I thought exponential growth would be nice */

        masc_log_message( MAS_VERBLVL_DEBUG, "mix: allocating a new 'sinks' array; the old one is too short" );
        tmp = masc_rtalloc( state->n_sinks_allocated * sizeof(struct sink_info*) );
        if( !tmp )
            return mas_error( MERR_MEMORY );
        
/*         memcpy( tmp, state->sinks, state->n_sinks*sizeof(struct sink_info*) ); */
        for( i=0; i<state->n_sinks; i++ )
            tmp[i] = state->sinks[i];
        
        masc_rtfree( state->sinks );
        state->sinks = tmp;
    }
    
    state->sinks[state->n_sinks] = masc_rtalloc( sizeof(struct sink_info) );
    if( !state->sinks[state->n_sinks] )
        return mas_error( MERR_MEMORY );
    
    masd_set_port_name( portnum, s );
    
    state->sinks[state->n_sinks]->portnum    = portnum;
    state->sinks[state->n_sinks]->multiplier = MAX_VOLUME_MULTIPLIER;
    state->sinks[state->n_sinks]->write_head = state->mix_gap;
    state->sinks[state->n_sinks]->samples_in = 0;
    state->sinks[state->n_sinks]->channels   = ch;
    state->sinks[state->n_sinks]->bits_per_sample_and_channel = res;
    switch( res )
    {
    case 8:
        state->sinks[state->n_sinks]->container_size_bytes = 1;
        break;
    case 16:
        state->sinks[state->n_sinks]->container_size_bytes = 2;
        break;
    case 20:
    case 24:
        state->sinks[state->n_sinks]->container_size_bytes = 4;
        break;
    default:
        return mas_error(MERR_INVALID); break;
    }

    if( !strcmp(dc->values[fi], "linear") )
        state->sinks[state->n_sinks]->format = MAS_LINEAR_FMT;
    else
        state->sinks[state->n_sinks]->format = MAS_ULINEAR_FMT;
        

    choose_mix_func( state, state->n_sinks );
    
    
#ifdef DEBUG
    state->sinks[state->n_sinks]->headstate  = 2;
#endif
    state->n_sinks                          += 1;
    

    /** retrieve dynamic mas ports and store in node */
    err = masd_get_dynport( &state->dp_pool, device_instance, state->reaction, &new_default_portnum);
    if (err < 0)
    {
	masc_logerror( err|MAS_ERR_CRITICAL, "couldn't retrieve dynamic port");
	return err;
    }
    err = masd_set_port_type( new_default_portnum, MAS_SINK );    
    err = masd_set_port_name( new_default_portnum, "default_mix_sink" );    
    err = masd_get_cmatrix_from_name( device_instance, "mas_mix_cmatrix_audio",
                                      &cmatrix ); 
    if ( err < 0 ) return err;
    err = masd_set_port_cmatrix ( new_default_portnum, cmatrix );

    /* schedule polling action for the port we were called for */
    predicate_portnum         = masc_rtalloc( sizeof (int32) );
    *predicate_portnum        = portnum;
    dataflow_port_dependency  = masc_rtalloc( sizeof (int32) );
    *dataflow_port_dependency = portnum;
    
    err = masd_reaction_queue_action(state->reaction, device_instance, 
                                     "mas_mix_mix", predicate_portnum,
                                     sizeof (int32), 
                                     0, 0, 0,
                                     MAS_PRIORITY_DATAFLOW, 1, 1, 
                                     dataflow_port_dependency);
    
    return 0;
}


/***************************************************************************
 * mas_dev_disconnect_port
 *
 *  predicate: int32 portnum
 *
 *  this just removes the sink whose portnum is passed from the buffering
 *  thingie. The samples that were already added to the buffer will now
 *  be wrongly divided by (n_sinks-1) instead of (n_sinks). I don't care
 *  right now.
 *  
 * returns: error
 *
 ***************************************************************************/
int32 mas_dev_disconnect_port( int32 device_instance, void* predicate )
{
  struct mix_state*  s;
  int32 portnum = *(int32*)predicate;
  int sinkno, i;
  
  masd_get_state(device_instance, (void**)&s);

  /* take no action for source disconnect */
  if ( portnum == s->source )
      return 0;
  
  sinkno = find_sink(s, portnum);
  if( sinkno<0 )
  {
      masc_log_message( 0, "mix: mas_dev_disconnect_port: no such port number %d\n", portnum );
      return mas_error( MERR_INVALID );
  }
  
  /* free the struct */
  masc_rtfree( s->sinks[sinkno] );

  /* there's a hole in our array; close it by shifting later sinks by one */
  for (i=sinkno; i<s->n_sinks-1; i++)
  {
      s->sinks[i] = s->sinks[i+1];
  }
  s->n_sinks -= 1;

  /* just to be clean */
  s->sinks[s->n_sinks] = NULL;

  /* recycle the sink port */
  masd_recycle_dynport( &s->dp_pool, device_instance, s->reaction, portnum );
     
  return 0;
}

/***************************************************************************
 * mas_mix_attenuate_linear
 *
 *  predicate: struct multiplier_and_portnum
 *
 *
 ***************************************************************************/
/* int32 mas_mix_attenuate_linear( int32 device_instance, void* predicate ) */
/* { */
/*     struct mix_state*  s; */
/*     struct mas_package* package; */
/*     int sinkno; */
/*     int32 portnum; */
/*     double multiplier; */
    
/*     masd_get_state(device_instance, (void**)&s); */
/*     masc_make_package_from_payload( &package, predicate ); */
/*     masc_pull_int32( package, &portnum ); */
/*     masc_pull_double( package, &multiplier ); */
/*     sinkno = find_sink(s, portnum); */
/*     if( sinkno<0 ) */
/*     { */
/*         masc_log_message( 0, "mix: mas_mix_attenuate_linear: no such port number %d\n", portnum ); */
/*         return mas_error( MERR_INVALID ); */
/*     } */

/*     s->sinks[sinkno]->multiplier= 100 * multiplier; */
    
/*     /\* null out the contents before destroying *\/ */
/*     package->contents = 0; */
/*     masc_destroy_package( package ); */
    
/*     return 0; */
/* } */


/* /\*************************************************************************** */
/*  * mas_mix_attenuate_decibel */
/*  * */
/*  *  predicate: struct multiplier_and_portnum */
/*  * */
/*  * */
/*  ***************************************************************************\/ */
/* int32 mas_mix_attenuate_decibel( int32 device_instance, void* predicate ) */
/* { */
/*     struct mix_state*  s; */
/*     struct mas_package* package; */
/*     int sinkno; */
/*     int32 portnum; */
/*     double db, m; */
    
    
/*     masd_get_state(device_instance, (void**)&s); */
/*     masc_make_package_from_payload( &package, predicate ); */
/*     masc_pull_int32( package, &portnum ); */
/*     masc_pull_double( package, &db ); */
/*     sinkno = find_sink(s, portnum); */
/*     if( sinkno<0 ) */
/*     { */
/*         masc_log_message( 0, "mix: mas_mix_attenuate_decibel: no such port number %d\n", portnum ); */
/*         return mas_error( MERR_INVALID ); */
/*     } */
    
/*     if ( db<0 ) */
/*         db = -db; */

/*     m = db / 20.0;     */
/*     m = pow(10,-m); */
    
/*     s->sinks[sinkno]->multiplier = 100 * m; */
    
/*     /\* null out the contents before destroying *\/ */
/*     package->contents = 0; */
/*     masc_destroy_package( package ); */
/*     return 0; */
/* } */

/***************************************************************************
 * mas_mix_
 *
 *  predicate: struct multiplier_and_portnum
 *
 *
 ***************************************************************************/
int32 mas_mix_set_portname( int32 device_instance, void* predicate )
{
    struct mix_state*  s;
    struct mas_package package;
    int32 portnum;
    char *new_name;
    
    
    masd_get_state(device_instance, (void**)&s);

    masc_setup_package( &package, predicate, 0, MASC_PACKAGE_STATIC|MASC_PACKAGE_EXTRACT );
    masc_pull_int32( &package, &portnum );
    masc_pull_string( &package, &new_name, FALSE );
    
    masd_set_port_name( portnum, new_name );
    masc_strike_package( &package );
    
    return 0;
}



/***************************************************************************
 * mas_dev_show_state - standard debugging action
 *
 *  predicate: unused
 *
 * Sends state information to stdout.
 *  
 * returns: error
 *
 ***************************************************************************/
int32
mas_dev_show_state( int32 device_instance, void* predicate )
{
    struct mix_state* state;

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

    return 0;
}


/***************************************************************************
 * mas_get
 ****************************************************************************/
int32
mas_get( int32 device_instance, void* predicate )
{
    struct mix_state*    state;
    int32                err;
    int32                retport;
    int32                return_value = 0;
    char*                key;
    struct mas_package   arg;
    struct mas_package   r_package;
    int                  i,j;
    int                  n=0;
/*     char                 sname[32]; */
    
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "list", "srate", "channels", "mix_gap", "sinksinfo", "out_packet_size", "mc_clkid", "samples_in", "multiplier", "do_soft_limiting", "threshold", "do_dither", "max_idle_time_ms", "gain_db", "" };

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

    /* Use the standard get_nugget wrapper. */
    err = masd_get_pre( predicate, &retport, &key, &arg );
    if ( err < 0 )
    {
        return_value = err;
        goto done;
    }
    

    /* construct our response */
    masc_setup_package( &r_package, NULL, 0, MASC_PACKAGE_NOFREE );
    
    /* count the defined nuggets */
    while ( *nuggets[n] != 0 ) n++;

    i = masc_get_string_index(key, nuggets, n);

    switch(i)
    {
    case 0: /*list*/
        masc_push_strings( &r_package, nuggets, n );
        break;
    case 1: /*srate*/
        masc_pushk_uint16( &r_package, "srate", state->srate );
        break;
    case 2: /*channels*/
        masc_pushk_uint8( &r_package, "channels", state->channels );
        break;
    case 3: /*mix_gap*/
        masc_pushk_int16( &r_package, "mix_gap", state->mix_gap );
        break;
    case 4: /*sinksinfo*/
    {
        struct mas_package sinkpack;
        char spbuf[1024];
        char *pname = NULL;

        for( j=0; j<state->n_sinks; j++ )
        {
            masc_setup_package( &sinkpack, spbuf, sizeof spbuf, MASC_PACKAGE_STATIC );
            masd_get_port_name( state->sinks[j]->portnum, &pname );
            masc_pushk_uint8( &sinkpack, "s", (uint8)j );
            masc_pushk_string( &sinkpack, "n", pname );
            masc_pushk_int32( &sinkpack, "pn", state->sinks[j]->portnum );
            masc_pushk_int16( &sinkpack, "m", state->sinks[j]->multiplier );
            masc_pushk_int16( &sinkpack, "db", get_masdb_from_multiplier(state->sinks[j]->multiplier) );
            masc_finalize_package( &sinkpack );
            masc_push_package( &r_package, &sinkpack );
            masc_strike_package( &sinkpack );
        }
        break;
    }
    case 5: /*out_packet_size*/
        masc_pushk_uint32(&r_package, "out_packet_size",state->out_packet_size);
        break;

    case 6: /*mc_clkid*/
        masc_pushk_int32(&r_package, "mc_clkid", state->poll_clock_id);
        break;

    case 7: /*samples_in*/
    {
        int32 portnum;
        int sink;
        
        if ( arg.contents == NULL )
        {
            return_value = mas_error(MERR_NULLPTR);
            break;
        }
        masc_pull_int32( &arg, &portnum );
        sink = find_sink( state, portnum );
        if( sink<0 )
        {
            masc_log_message( 0, "mix: mas_get: no such port number %d\n", portnum );
            return_value = mas_error(MERR_NULLPTR);
            break;
        }

        /* printf( "sink %d has portnum %d\n", sink, portnum ); */
        
        if( sink<0 )
        {
            return_value = sink;
            break;
        }
        
        masc_pushk_uint32( &r_package, "samples_in", state->sinks[sink]->samples_in );

        break;
    }

    case 8: /*multiplier*/
    {
        int32 portnum;
        int sink;
        
        if ( arg.contents == NULL )
        {
            return_value = mas_error(MERR_NULLPTR);
            break;
        }
        masc_pull_int32( &arg, &portnum );
        sink = find_sink( state, portnum );
        if( sink<0 )
        {
            masc_log_message( 0, "mix: mas_get: no such port number %d\n", portnum );
            return_value = mas_error(MERR_INVALID);
            break;
        }

        if( sink<0 )
        {
            return_value = sink;
            break;
        }
        
        masc_pushk_uint16( &r_package, "multiplier", state->sinks[sink]->multiplier );

        break;
    }

    case 9: /*do_soft_limiting*/
        masc_pushk_int8( &r_package, "do_soft_limiting", state->do_soft_limiting );
        break;

    case 10: /*threshold*/
        masc_pushk_double( &r_package, "threshold", state->limiter.db_threshold );
        break;

    case 11: /*do_dither*/
        masc_pushk_int8( &r_package, "do_dither", state->do_dither );
        break;

    case 12: /*max_idle_time_ms*/
        masc_pushk_uint32( &r_package, "max_idle_time_ms", state->max_idle_time_ms );
        break;

    case 13: /*gain_db*/
    {
        int32 portnum;
        int sink;
        
        if ( arg.contents == NULL )
        {
            return_value = mas_error(MERR_NULLPTR);
            break;
        }
        masc_pull_int32( &arg, &portnum );
        sink = find_sink( state, portnum );
        if( sink<0 )
        {
            masc_log_message( 0, "mix: mas_get: no such port number %d\n", portnum );
            return_value = mas_error(MERR_INVALID);
            break;
        }

        if( sink<0 )
        {
            return_value = sink;
            break;
        }
        
        masc_pushk_int16( &r_package, "gain_db", get_masdb_from_multiplier(state->sinks[sink]->multiplier) );
        
        break;
    }
    
    default:
        return_value = mas_error(MERR_INVALID);
        break;
    }

    
done:
    /* return an error */
    if ( return_value < 0 )
        masc_pushk_int32( &r_package, "err", return_value );

    masc_finalize_package( &r_package );

    /* post the response where it belongs and free the data structures
     * we abused */
    masd_get_post( state->reaction, retport, key, &arg, &r_package );
        
    return return_value;
}


/***************************************************************************
 * mas_set
 ****************************************************************************/
int32
mas_set( int32 device_instance, void* predicate )
{
    struct mix_state*   state;
    int32               err, return_value = 0;
    char               *key;
    struct mas_package  arg;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "mix_gap", "out_packet_size", "mc_clkid", "multiplier", "do_soft_limiting", "threshold", "do_dither", "max_idle_time_ms", "gain_db", "" };
    int i, j, n=0;
    int16  getter;
    uint32 new_period;   
    int32  new_clock;
    double thr;
    
    masd_get_state(device_instance, (void**)&state);

    /* Use the standard get_nugget wrapper. */
    err = masd_set_pre( predicate, &key, &arg );
    if ( err < 0 ) return err;

    /* count the defined nuggets */
    while ( *nuggets[n] != 0 ) n++;

    i = masc_get_string_index(key, nuggets, n);

    switch(i)
    {
    case 0: /*mix_gap*/
        masc_pullk_int16( &arg, "mix_gap", &getter );

        if( state->channels == 1 )
        {
            for( j=0; j<state->n_sinks; j++ )
            {
                state->sinks[j]->reference  += getter - state->mix_gap;
                state->sinks[j]->write_head += getter - state->mix_gap;
            }
        }
        else
        {
            for( j=0; j<state->n_sinks; j++ )
            {
                state->sinks[j]->reference  += 2 * (getter - state->mix_gap);
                state->sinks[j]->write_head += 2 * (getter - state->mix_gap);
            }
        }
        state->mix_gap = getter;
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: gap set to %d.", getter );
        break;

    case 1: /*out_packet_size*/
        masc_pullk_uint32( &arg, "out_packet_size", &new_period );
#ifdef sun
	/* The Solaris audio system has a bug where it must write buffers of data larger than 1K
	 * in order to generate an accurate number of samples that have been played. */
	if(new_period > DEFAULT_OUT_PACKET_SIZE)
	{
#endif
		state->new_out_packet_size = new_period;
		state->reschedule          = RESCHEDULE_PERIOD;
#ifdef sun
	}
#endif
        break;

    case 2: /*mc_clkid*/
        masc_pullk_int32( &arg, "mc_clkid", &new_clock );
        state->poll_clock_id    = new_clock;
/*         state->force_poll_clock_id = new_clock; */
        state->reschedule       = RESCHEDULE_CLOCK;
        break;

    case 3: /*multiplier*/
    {
        int32 portnum;
        uint16 multiplier;
        int sink;
        
        if ( arg.contents == NULL )
        {
            return_value = mas_error(MERR_INVALID);
            break;
        }
        masc_pull_int32( &arg, &portnum );
        masc_pull_uint16( &arg, &multiplier );
        sink = find_sink( state, portnum );
        
        if( sink<0 )
        {
            masc_log_message( 0, "mix: mas_set: no such port number %d\n", portnum );
            return_value = mas_error( MERR_INVALID );
            break;
        }
        
        state->sinks[sink]->multiplier = multiplier;
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: sink %d multiplier set to %d.", sink, multiplier );
        break;
    }

    case 4: /*do_soft_limiting*/
        /* it's a little wasteful, but we don't want to keep the
           memory around if it's not needed */
        if( state->do_soft_limiting )
            exit_limiter( state );
        
        masc_pullk_int8( &arg, "do_soft_limiting", &state->do_soft_limiting );

        if( state->do_soft_limiting )
            init_limiter( state, state->limiter.db_threshold, state->limiter.ymax );
        
        choose_fill_func( state );
        
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: doing %s sample limiting.", (state->do_soft_limiting)?"SOFT":"HARD" );
        break;

    case 5: /*threshold*/
        masc_pullk_double( &arg, "threshold", &thr );

        if( thr>=0 )
        {
            masc_log_message( 0, "mix: threshold must be <0" );
            return_value = mas_error( MERR_INVALID );
        }
        exit_limiter( state );
        init_limiter( state, thr, (1<<(19+MAX_VOL_SHIFT))-1 );

        /* /\* if you'd like to print the limiter curve... *\/ */
/*         { */
/*             int i; */
/*             struct limiter *l = &state->limiter; */
/*             printf( "#### %d\n", l->threshold ); */
/*             for( i=0/\* 3*l->threshold/4 *\/; i<1.25*l->xmax; i+=10000 ) */
/*                 printf( "%d %d\n", i, limit_sample(l, i) ); */
/*         } */
    
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: soft limiting threshold set to %fdbFS.", state->limiter.db_threshold );
        break;

    case 6: /*do_dither*/
        if( state->do_dither )
            exit_dither( state );

        masc_pullk_int8( &arg, "do_dither", &state->do_dither );

        if( state->do_dither )
            init_dither( state );
        
        choose_fill_func( state );
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: dithering of lsb turned %s.", (state->do_dither)?"ON":"OFF" );
        break;

    case 7: /*max_idle_time_ms*/
        masc_pullk_uint32( &arg, "max_idle_time_ms", &state->max_idle_time_ms );
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: maximum idle time is now %d ms.", state->max_idle_time_ms );
        break;

    case 8: /*gain_db*/
    {
        int32 portnum;
        int16 gain_db;
        int sink;
        
        if ( arg.contents == NULL )
        {
            return_value = mas_error(MERR_INVALID);
            break;
        }
        masc_pull_int32( &arg, &portnum );
        masc_pull_int16( &arg, &gain_db );
        sink = find_sink( state, portnum );
        
        if( sink<0 )
        {
            masc_log_message( 0, "mix: mas_set: no such port number %d\n", portnum );
            return_value = mas_error( MERR_INVALID );
            break;
        }

        if( gain_db > 0 )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "mix: warning: gain_db>0 means amplification, and may result in distortion." );
        }
        
        if( gain_db <= (DB_CUTOFF*10) )
        {
            state->sinks[sink]->multiplier = 0;
        }
        else
        {
            state->sinks[sink]->multiplier = (int16)((double)MAX_VOLUME_MULTIPLIER * pow(10.0,(double)gain_db/200.0) );
        }
        
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: sink %d multiplier set to %d (==%fdb).", sink, state->sinks[sink]->multiplier, (double)gain_db/10.0 );
        break;
    }
    
    default:
        break;
    }

    /* cleanup after our mess */
    masd_set_post( key, &arg );
    return return_value;
}


void
fill_segment_soft_nod( struct mix_state *state, int16 *buf )
{
    int32 *tbl;
    int32 i, pos, tmp, a, sign, shift, thr, xmax, ymax;

    tbl   = state->limiter.tbl;
    thr   = state->limiter.threshold;
    shift = state->limiter.shift;
    xmax  = state->limiter.xmax;
    ymax  = state->limiter.ymax;
    
    if( state->channels == 1 )
    {
        /* mono */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos = (state->cb.read_head+i)%CIRCBUF_SIZE;
            tmp = state->cb.buf[ pos ];
            a = abs(tmp);
            sign = (tmp>0)?1:-1;
            buf[i] = ((a<=thr)? tmp : ( (a>=xmax)? sign*ymax : sign*tbl[(a-thr)>>shift] )) >> 11;
            
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos ] = 0;
        }
        state->cb.read_head += state->out_packet_size;
    }
    else
    {
        /* stereo */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos = (state->cb.read_head + 2*i)%CIRCBUF_SIZE;
            
            tmp = state->cb.buf[ pos ];
            a = abs(tmp);
            sign = (tmp>0)?1:-1;
            buf[2*i] = ((a<=thr)? tmp : ( (a>=xmax)? sign*ymax : sign*tbl[(a-thr)>>shift] )) >> 11;
                        
            tmp = state->cb.buf[ (pos+1)%CIRCBUF_SIZE ];
            a = abs(tmp);
            sign = (tmp>0)?1:-1;
            buf[2*i+1] =  ((a<=thr)? tmp : ( (a>=xmax)? sign*ymax : sign*tbl[(a-thr)>>shift] )) >> 11;
                        
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos++ ] = 0;
            state->cb.buf[ pos % CIRCBUF_SIZE ]=0;
        }
        state->cb.read_head += 2*state->out_packet_size;
    }
}

void
fill_segment_soft_d( struct mix_state *state, int16 *buf )
{
    int32 *tbl;
    int32 i, pos, tmp, a, sign, shift, thr, xmax, ymax, dpos;

    tbl   = state->limiter.tbl;
    thr   = state->limiter.threshold;
    shift = state->limiter.shift;
    xmax  = state->limiter.xmax;
    ymax  = state->limiter.ymax;
        
    dpos = state->dither.pos;

    if( state->channels == 1 )
    {
        /* mono */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos = (state->cb.read_head+i)%CIRCBUF_SIZE;
            tmp = state->cb.buf[ pos ] + state->dither.tbl[dpos++];
            a = abs(tmp);
            sign = (tmp>0)?1:-1;
            tmp = ((a<=thr)? tmp : ( (a>=xmax)? sign*ymax : sign*tbl[(a-thr)>>shift] ));
            buf[i] = tmp>>11;
            
            dpos %= state->dither.tbl_size;
            
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos ] = 0;
        }
        state->cb.read_head += state->out_packet_size;
    }
    else
    {
        /* stereo */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos  = (state->cb.read_head + 2*i)%CIRCBUF_SIZE;
            tmp = state->cb.buf[ pos ] + state->dither.tbl[dpos++];
            a = abs(tmp);
            sign = (tmp>0)?1:-1;
            tmp = ((a<=thr)? tmp : ( (a>=xmax)? sign*ymax : sign*tbl[(a-thr)>>shift] ));
            buf[2*i] = tmp>>11;
            dpos %= state->dither.tbl_size;
            
            tmp = state->cb.buf[ (pos+1)%CIRCBUF_SIZE ] + state->dither.tbl[dpos++];
            a = abs(tmp);
            sign = (tmp>0)?1:-1;
            tmp = ((a<=thr)? tmp : ( (a>=xmax)? sign*ymax : sign*tbl[(a-thr)>>shift] ));
            buf[2*i+1] = tmp>>11;
            dpos %= state->dither.tbl_size;
            
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos++ ] = 0;
            state->cb.buf[ pos % CIRCBUF_SIZE ]=0;
        }
        state->cb.read_head += 2*state->out_packet_size;
    }
    state->dither.pos = dpos;
}

void
fill_segment_hard_nod( struct mix_state *state, int16 *buf )
{
    int32 i, pos, tmp;

    if( state->channels == 1 )
    {
        /* mono */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos = (state->cb.read_head+i)%CIRCBUF_SIZE;
            tmp = state->cb.buf[ pos ] >>11;
            buf[i] = clamp16( tmp );
                        
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos ] = 0;
        }
        state->cb.read_head += state->out_packet_size;
    }
    else
    {
        /* stereo */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos = (state->cb.read_head + 2*i)%CIRCBUF_SIZE;
            tmp = state->cb.buf[ pos ] >>11;
            buf[2*i]   = clamp16( tmp );
            tmp = state->cb.buf[ (pos+1)%CIRCBUF_SIZE ] >>11;
            buf[2*i+1] = clamp16( tmp );
                        
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos++ ] = 0;
            state->cb.buf[ pos % CIRCBUF_SIZE ]=0;
        }
        state->cb.read_head += 2*state->out_packet_size;
    }
}

void
fill_segment_hard_d( struct mix_state *state, int16 *buf )
{
    int32 i, pos, tmp, dpos;

    dpos = state->dither.pos;

    if( state->channels == 1 )
    {
        /* mono */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos = (state->cb.read_head+i)%CIRCBUF_SIZE;
            tmp = ( state->cb.buf[ pos ] + state->dither.tbl[ dpos++ ] ) >> 11;
            buf[i] = clamp16( tmp );
            dpos %= state->dither.tbl_size;
            
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos ] = 0;
        }
        state->cb.read_head += state->out_packet_size;
    }
    else
    {
        /* stereo */
        for( i=0; i<state->out_packet_size; i++ )
        {
            pos = (state->cb.read_head + 2*i)%CIRCBUF_SIZE;
            tmp = (state->cb.buf[ pos ] + state->dither.tbl[ dpos++ ] )>>11;
            buf[2*i]   = clamp16( tmp );
            dpos %= state->dither.tbl_size;
            tmp = (state->cb.buf[ (pos+1)%CIRCBUF_SIZE ]  + state->dither.tbl[ dpos++ ] ) >>11;
            buf[2*i+1] = clamp16( tmp );
            dpos %= state->dither.tbl_size;
            
            /* TODO: do this in a more efficient way */
            state->cb.buf[ pos++ ] = 0;
            state->cb.buf[ pos % CIRCBUF_SIZE ]=0;
        }
        state->cb.read_head += 2*state->out_packet_size;
    }
    state->dither.pos = dpos;
}


/***************************************************************************
 * mas_mix_poll
 * 
 *  - the periodic action that takes data from the ring buffer and sends
 *    it away
 ****************************************************************************/
int32
mas_mix_poll( int32 device_instance, void* predicate )
{
    struct mix_state* state;
    struct mas_data*  data;
    int               n_sinks_late = 0;
    int16 *buf;
    int i;
    int32 delta;    
    uint32 mcnow;
    uint32 max_idle;
    
    masd_get_state(device_instance, (void**)&state);

    masd_mc_val( state->poll_clock_id, &mcnow );
    masd_sch_action_delta( &delta );
    
    if( abs(delta) > state->srate )
    {
        /* For now, I don't believe delta's over 1 second! */
        masc_log_message( 0, "mix: ignoring outrageous poll delta of %d", delta );
        state->exact_last_poll_time = mcnow;       
    }
    else
    {
        /* the normal thing */
        state->exact_last_poll_time = mcnow - delta;
    }   
    
    
    if( !(state->config_state & ALL_SINKS_SLEEPING) )
    {
        /* one ore more sinks are not sleeping */
        data = MAS_NEW( data );
                
        data->header.media_timestamp = state->media_ts;
        state->media_ts += state->out_packet_size; /* put this outside the 'if' ? */
        
        data->header.sequence=state->sequence;
        state->sequence += 1;


        if( state->config_state & NEED_MARK )
        {
            data->header.mark   = 1;
            state->config_state &= ~NEED_MARK;
        }
    
        i = state->out_packet_size * state->channels * sizeof(int16);
        
        data->length           = i;
        data->allocated_length = i;
        data->segment          = masc_rtalloc( i );
        
        buf = (int16*)data->segment;
        
        /* Should we, and if so how, check whether the read head is
           running away from the write heads? */
        
#ifdef DEBUG
        for( i=0; i<state->n_sinks; i++ )
        {
            if( (state->sinks[i]->write_head/* + 2*state->mix_gap */ ) < (state->cb.read_head + state->channels*state->out_packet_size)  )
            {
                if( state->sinks[i]->headstate != 0 )
                    masc_log_message( MAS_VERBLVL_DEBUG,
                                      "mix: read head is starting to run away from write head #%d.\n(%lu samples missing for a full output packet). Will report if/when write head catches up.", i, (state->cb.read_head + state->channels*state->out_packet_size - state->sinks[i]->write_head) / state->channels );
                state->sinks[i]->headstate = 0;
            }
            else
            {
                if( state->sinks[i]->headstate == 0 )
                    masc_log_message( MAS_VERBLVL_DEBUG,
                                      "mix: write head #%d has caught up again. phew!\n", i );
                state->sinks[i]->headstate = 1;
            }
        }
#endif

        
        /* see whether all sinks have not received data for a while
         * (say, one duration of the circ buffer) */
        max_idle = state->channels * state->max_idle_time_ms * state->srate / 1000;

        /* ...avoid the calculation when close to a UINT32 boundary */
        if( UINT32_MAX-state->cb.read_head > max_idle )
        {
            for( i=0; i<state->n_sinks; i++ )
            {
                if( UINT32_MAX-state->sinks[i]->write_head > max_idle )
                {
                    /* don't do it if either read or write head have wrapped but not the other */
                    if( ((state->sinks[i]->write_head > state->cb.read_head) && (state->sinks[i]->write_head - state->cb.read_head < HALF_UINT32_MAX)) || (state->cb.read_head - state->sinks[i]->write_head < HALF_UINT32_MAX) )
                    {
                        if( state->sinks[i]->write_head + max_idle < state->cb.read_head )
                        {
                            ++n_sinks_late;
                        }
                    }
                }
            }
        }
        
        if( n_sinks_late != state->n_sinks )
        {
            /* this will fill the buffer according to our limiting/dithering preferences */
            state->fill_segment( state, buf );
                        
            /*             fwrite( buf, sizeof(int16), 2*state->out_packet_size, fp ); */
            masd_post_data( state->source, data );
        }
        else
        {
            /* all sinks are very late.. don't send anything */
            if( !state->all_sinks_late_msg_sent )
            {
                masc_log_message( MAS_VERBLVL_DEBUG, "mix: all input sinks have been quiet for a while... I'll stop sending 0's now.\n" );
                state->all_sinks_late_msg_sent = 1; /* this will be revoked when a mark bit comes in */
                state->config_state |= ALL_SINKS_SLEEPING;
                masc_rtfree( data->segment );
                masc_rtfree( data );
            }
        }
        
    }
    
    if( state->reschedule != RESCHEDULE_NONE )
    {
        struct mas_package  r_package;
        
        /* That's never going to happen, right??? */
        mas_assert( state->reschedule < (RESCHEDULE_PERIOD|RESCHEDULE_CLOCK), "race condition; please reschedule clock and period one after the other with at least one output packet time difference." );
        
        if( state->reschedule == RESCHEDULE_PERIOD )
        {
            masc_setup_package( &r_package, NULL, 0, MASC_PACKAGE_NOFREE );
            masc_pushk_uint32( &r_package, "x", state->new_out_packet_size );
            masc_finalize_package( &r_package );
            
            masc_log_message( MAS_VERBLVL_DEBUG, "mix: new polling period (clock %d) will be %d.", state->poll_clock_id, state->new_out_packet_size );

            masd_reaction_queue_action_simple(
                state->reaction,
                MAS_SCH_INSTANCE,
                "mas_sch_set_event_period",
                r_package.contents, 
                r_package.size
                );
            masc_strike_package( &r_package );
            state->out_packet_size = state->new_out_packet_size;
        }
        
        if( state->reschedule == RESCHEDULE_CLOCK )
        {
            masc_setup_package( &r_package, NULL, 0, MASC_PACKAGE_NOFREE );
            masc_pushk_int32( &r_package, "x", state->poll_clock_id );
            masc_finalize_package( &r_package );
            
            masc_log_message( MAS_VERBLVL_DEBUG, "mix: syncing mixer poll action with clock %d.", state->poll_clock_id );

            masd_reaction_queue_action_simple(
                state->reaction,
                MAS_SCH_INSTANCE,
                "mas_sch_set_event_clkid",
                r_package.contents, 
                r_package.size
                );
            masc_strike_package( &r_package );
        }

        /* all done reschedulin' */
        state->reschedule = RESCHEDULE_NONE;
    }
    
    return 0;
}


/***************************************************************************
 * mas_mix_mix
 * 
 * - mix incoming data into the circular buffer
 ***************************************************************************/
int32
mas_mix_mix( int32 device_instance, void* predicate )
{
    struct mix_state  *s;
    struct mas_data   *data;
    int               current_sink;
    int32             portnum;
    uint32            write_pos;
    int32             delta;
    uint32            mcnow;
    

    masd_get_state(device_instance, (void**)&s);    
    
    /* the port number is in the predicate. hehe! */
    portnum = *(int32*)predicate;
    
    /* fish out its index in the array of port numbers */ 
    current_sink = find_sink( s, portnum );
    if( current_sink<0 )
    {
        masc_log_message( 0, "mix: mas_mix_mix: no such port number %d\n", portnum );
        return mas_error( MERR_INVALID );
    }

    /* fetch data */
    masd_get_data( portnum, &data );

    s->sinks[current_sink]->samples_in += data->length / (s->sinks[current_sink]->container_size_bytes * s->sinks[current_sink]->channels);
    
    
    /* try this: if first incident packet from anywhere doesn't have
       the mark bit set, we pretend that it did. */
    if( s->config_state & ALL_SINKS_SLEEPING )
    {
        if( !(data->header.mark) )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "mix: first incident packet... generating mark flag\n" );
            data->header.mark = 1;
        }
        /* no longer sleeping, but need a mark bit on the first
           outgoing packet */
        s->config_state = NEED_MARK;
    }
    
    /* if mark bit set, mix in at "now + mix_gap ms" */ 
    if( data->header.mark )
    {
        masd_mc_val( s->poll_clock_id, &mcnow );
        delta = mcnow - s->exact_last_poll_time;

/*         if( (delta>100) || (delta<-100) ) */
/*         { */
/*             printf("Could this be it: %d\n", delta); */
/*         } */
        
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: got a packet with mark flag; resetting reference of sink%d", current_sink );
        masc_log_message( MAS_VERBLVL_DEBUG, "mix: mcnow %u - exact_last_pool_time %u = delta %d", mcnow, s->exact_last_poll_time, delta );

        s->sinks[current_sink]->reference  = s->cb.read_head + s->channels * (s->mix_gap + delta - data->header.media_timestamp);
        s->sinks[current_sink]->write_head = s->sinks[current_sink]->reference + s->channels*data->header.media_timestamp;
        s->all_sinks_late_msg_sent = 0;
    }


    write_pos = s->sinks[current_sink]->reference + s->channels*data->header.media_timestamp;
    
    mix_if_needed( s, data, current_sink, write_pos );
    
    masc_strike_data(data);
    masc_rtfree(data);
    data = 0;

    return 0;
}



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

int find_sink(struct mix_state *s, int32 portnum)
{
    int i;
    int sinkno = -1;
    
    for(i=0; i<s->n_sinks; i++)
    {
        if (s->sinks[i]->portnum==portnum)
        {
            sinkno = i;
            break;
        }
    }
    if ( sinkno < 0 )
        return mas_error(MERR_NOTDEF);
    
    return sinkno;
}


void
mix_if_needed( struct mix_state *s, struct mas_data *data,
               int current_sink, uint32 write_pos)
{
    int diff, len, nlen, nlen2;
    
    len = data->length / s->sinks[current_sink]->container_size_bytes;
    
    /* only bother mixing in if not too old 
       (and always mix when the media time stamps wrap) */
    if( ((write_pos+len) > (s->cb.read_head)) || (s->cb.read_head-write_pos > HALF_UINT32_MAX) ) 
    {
        /* and don't if it's too new either */
        if( (write_pos + len - s->cb.read_head) < CIRCBUF_SIZE )
        {
            if( (write_pos < s->cb.read_head) && ((write_pos+len) > s->cb.read_head))
            {
                /* it's part behind, part in front of read head */
                /* manipulate values so that the circular_buffer_mix
                 * routine doesn't notice we're only mixing in part of
                 * a packet hehehe */
                diff = s->cb.read_head - write_pos;
                write_pos = s->cb.read_head;
#ifdef DEBUG
                masc_log_message( 0, "mix: part of incoming data too old (%lu samples are too old)", diff );
#endif

                nlen = nlen2 = len - diff;
                
                s->sinks[current_sink]->circular_buffer_mix( &s->cb, write_pos % CIRCBUF_SIZE,
                                                             data->segment+diff*s->sinks[current_sink]->container_size_bytes, &nlen,
                                                             s->sinks[current_sink]->multiplier );

                /* got to make the length right... we could have been moving between mono<->stereo */
                len = len * nlen / nlen2;
            }
            else
            {
                /* this is the normal case... mix in entire packet */
                s->sinks[current_sink]->circular_buffer_mix( &s->cb, write_pos % CIRCBUF_SIZE,
                                                             data->segment, &len, s->sinks[current_sink]->multiplier );
            }
        }
#ifdef DEBUG
        else
        {
            masc_log_message(0, "mix: incoming data too new (%lu samples in the future, but ring buffer is only %lu long)", (write_pos + len - s->cb.read_head) / s->channels, CIRCBUF_SIZE / s->channels);
        }
#endif
    }
#ifdef DEBUG
    else
    {
        masc_log_message(0, "mix: incoming data too old; its last sample is %lu samples behind read position [difference in read/write positions between this and the previous packet: read: %lu samples, write: %lu samples]", (s->cb.read_head-write_pos-len)/s->channels, (s->cb.read_head - s->prev_rh)/s->channels, (write_pos+len-s->prev_wh)/s->channels );
    }
    s->prev_wh = write_pos+len;
    s->prev_rh = s->cb.read_head;
#endif
    
    s->sinks[current_sink]->write_head +=  len;
    
    return;
}





/* These are separate functions because the 'data' array has to be of a
 * different type for each of these. I didn't want a conditional in
 * the innermost loop... so we'll have to live with this */

void
circular_buffer_mix_int16( struct circular_buffer *cb, uint32 write_head,
                           char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    int16 *data = (int16*)segment;
    
    buf = cb->buf;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
                buf[ write_head+i ] += multiplier * (buffer_t)data[i] << 4;
        
        for( i=0; i<remain; i++ )
            buf[i] += multiplier * (buffer_t)data[ fit+i ] << 4;   
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
            buf[ write_head+i ] += multiplier * (buffer_t)data[i] << 4;
    }
}

void
circular_buffer_mix_int16_mtos( struct circular_buffer *cb, uint32 write_head,
                                char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    int16 *data = (int16*)segment;
    
    buf = cb->buf;
    *length *= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i+=2 )
            {
                tmp = multiplier * (buffer_t)data[i/2] << 4;
                buf[ write_head+i   ] += tmp;
                buf[ write_head+i+1 ] += tmp;                
            }
        
        for( i=0; i<remain; i+=2 )
        {
            tmp = multiplier * (buffer_t)data[ (fit+i)/2 ] << 4;
            buf[i  ] += tmp;
            buf[i+1] += tmp;
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i+=2 )
        {
            tmp = multiplier * (buffer_t)data[i/2] << 4;
            buf[ write_head+i   ] += tmp;
            buf[ write_head+i+1 ] += tmp;
        }
    }
}

void
circular_buffer_mix_int16_stom( struct circular_buffer *cb, uint32 write_head,
                           char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    int16 *data = (int16*)segment;
    
    buf = cb->buf;
    *length /= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
            {
                tmp = multiplier * ( (buffer_t)data[2*i] << 4 ) + ( (buffer_t)data[2*i+1] << 4 );
                buf[ write_head+i ] += tmp / 2;
            }
        
        for( i=0; i<remain; i++ )
        {
            tmp = multiplier * ( (buffer_t)data[2*(fit+i)] << 4 ) + ( (buffer_t)data[2*(fit+i)+1] << 4 );
            buf[i] += tmp / 2;   
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
        {
            tmp  = multiplier * ( (buffer_t)data[2*i] << 4 ) + ( (buffer_t)data[2*i+1] << 4 );
            /* this is ok, i.e. will not lose quality, because of the << 4 */
            buf[ write_head+i ] += tmp / 2;
        }
    }
}



void
circular_buffer_mix_int8( struct circular_buffer *cb, uint32 write_head,
                          char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    int8 *data = (int8*)segment;
    
    buf = cb->buf;
    remain = write_head + *length - CIRCBUF_SIZE;
    
    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
                buf[ write_head+i ] += multiplier * (buffer_t)data[i] << 12;
        
        for( i=0; i<remain; i++ )
            buf[i] += multiplier * (buffer_t)data[ fit+i ] << 12;   
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
            buf[ write_head+i ] += multiplier * (buffer_t)data[i] << 12;
    }
}

void
circular_buffer_mix_int8_mtos( struct circular_buffer *cb, uint32 write_head,
                                char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    int8 *data = (int8*)segment;
    
    buf = cb->buf;
    *length *= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i+=2 )
            {
                tmp = multiplier * (buffer_t)data[i/2] << 12;
                buf[ write_head+i   ] += tmp;
                buf[ write_head+i+1 ] += tmp;                
            }
        
        for( i=0; i<remain; i+=2 )
        {
            tmp = multiplier * (buffer_t)data[ (fit+i)/2 ] << 12;
            buf[i  ] += tmp;
            buf[i+1] += tmp;
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i+=2 )
        {
            tmp = multiplier * (buffer_t)data[i/2] << 12;
            buf[ write_head+i   ] += tmp;
            buf[ write_head+i+1 ] += tmp;
        }
    }
}

void
circular_buffer_mix_int8_stom( struct circular_buffer *cb, uint32 write_head,
                           char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    int8 *data = (int8*)segment;
    
    buf = cb->buf;
    *length /= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
            {
                tmp = multiplier * ( (buffer_t)data[2*i] << 12 ) + ( (buffer_t)data[2*i+1] << 12 );
                buf[ write_head+i ] += tmp / 2;
            }
        
        for( i=0; i<remain; i++ )
        {
            tmp = multiplier * ( (buffer_t)data[2*(fit+i)] << 12 ) + ( (buffer_t)data[2*(fit+i)+1] << 12 );
            buf[i] += tmp / 2;   
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
        {
            tmp  = multiplier * ( (buffer_t)data[2*i] << 12 ) + ( (buffer_t)data[2*i+1] << 12 );
            /* this is ok, i.e. will not lose quality, because of the << 4 */
            buf[ write_head+i ] += tmp / 2;
        }
    }
}


void
circular_buffer_mix_uint8( struct circular_buffer *cb, uint32 write_head,
                           char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    uint8 *data = (uint8*)segment;
    
    buf = cb->buf;
    remain = write_head + *length - CIRCBUF_SIZE;
    
    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
                buf[ write_head+i ] += multiplier * (buffer_t)(data[i]-128) << 12;
        
        for( i=0; i<remain; i++ )
            buf[i] += multiplier * (buffer_t)(data[ fit+i ]-128) << 12;   
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
            buf[ write_head+i ] += multiplier * (buffer_t)(data[i]-128) << 12;
    }
}


void
circular_buffer_mix_uint8_mtos( struct circular_buffer *cb, uint32 write_head,
                                char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    uint8 *data = (uint8*)segment;
    
    buf = cb->buf;
    *length *= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i+=2 )
            {
                tmp = multiplier * (buffer_t)(data[i/2]-128) << 12;
                buf[ write_head+i   ] += tmp;
                buf[ write_head+i+1 ] += tmp;                
            }
        
        for( i=0; i<remain; i+=2 )
        {
            tmp = multiplier * (buffer_t)(data[ (fit+i)/2 ]-128) << 12;
            buf[i  ] += tmp;
            buf[i+1] += tmp;
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i+=2 )
        {
            tmp = multiplier * (buffer_t)(data[i/2]-128) << 12;
            buf[ write_head+i   ] += tmp;
            buf[ write_head+i+1 ] += tmp;
        }
    }
}

void
circular_buffer_mix_uint8_stom( struct circular_buffer *cb, uint32 write_head,
                                char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    uint8 *data = (uint8*)segment;
    
    buf = cb->buf;
    *length /= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
            {
                tmp = multiplier * ( (buffer_t)(data[2*i]-128) << 12 ) + ( (buffer_t)(data[2*i+1]-128) << 12 );
                buf[ write_head+i ] += tmp / 2;
            }
        
        for( i=0; i<remain; i++ )
        {
            tmp = multiplier * ( (buffer_t)(data[2*(fit+i)]-128) << 12 ) + ( (buffer_t)(data[2*(fit+i)+1]-128) << 12 );
            buf[i] += tmp / 2;   
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
        {
            tmp  = multiplier * ( (buffer_t)(data[2*i]-128) << 12 ) + ( (buffer_t)(data[2*i+1]-128) << 12 );
            /* this is ok, i.e. will not lose quality, because of the << 4 */
            buf[ write_head+i ] += tmp / 2;
        }
    }
}


void
circular_buffer_mix_int20( struct circular_buffer *cb, uint32 write_head,
                           char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    int32 *data = (int32*)segment;    

    buf = cb->buf;
    remain = write_head + *length - CIRCBUF_SIZE;
    
    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
                buf[ write_head+i ] += multiplier * (buffer_t)data[i];
        
        for( i=0; i<remain; i++ )
            buf[i] += multiplier * (buffer_t)data[ fit+i ];   
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
            buf[ write_head+i ] += multiplier * (buffer_t)data[i];
    }
}


void
circular_buffer_mix_int20_mtos( struct circular_buffer *cb, uint32 write_head,
                                char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    int32 *data = (int32*)segment;    
    
    buf = cb->buf;
    *length *= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i+=2 )
            {
                tmp = multiplier * (buffer_t)data[i/2];
                buf[ write_head+i   ] += tmp;
                buf[ write_head+i+1 ] += tmp;                
            }
        
        for( i=0; i<remain; i+=2 )
        {
            tmp = multiplier * (buffer_t)data[ (fit+i)/2 ];
            buf[i  ] += tmp;
            buf[i+1] += tmp;
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i+=2 )
        {
            tmp = multiplier * (buffer_t)data[i/2];
            buf[ write_head+i   ] += tmp;
            buf[ write_head+i+1 ] += tmp;
        }
    }
}


void
circular_buffer_mix_int20_stom( struct circular_buffer *cb, uint32 write_head,
                           char *segment, int *length, int16 multiplier )
{
    buffer_t *buf;
    int remain, fit;
    uint32 i;
    buffer_t tmp;
    int32 *data = (int32*)segment;    

    buf = cb->buf;
    *length /= 2;
    remain = write_head + *length - CIRCBUF_SIZE;

    if( remain > 0 )
    {
        /* data will wrap over end of circular buffer */
        fit = CIRCBUF_SIZE - write_head;
        if( fit>0 )
            for( i=0; i<fit; i++ )
            {
                tmp = multiplier * ( (buffer_t)data[2*i] ) + ( (buffer_t)data[2*i+1] );
                buf[ write_head+i ] += tmp / 2;
            }
        
        for( i=0; i<remain; i++ )
        {
            tmp = multiplier * ( (buffer_t)data[2*(fit+i)] ) + ( (buffer_t)data[2*(fit+i)+1] );
            buf[i] += tmp / 2;   
        }
    }
    else
    {
        /* data will fit into remaining buffer space */
        for( i=0; i<*length; i++ )
        {
            tmp  = multiplier * ( (buffer_t)data[2*i] ) + ( (buffer_t)data[2*i+1] );
            /* this is ok, i.e. will not lose quality, because of the << 4 */
            buf[ write_head+i ] += tmp / 2;
        }
    }
}


#if 0 /* PRESERVE THIS FOR REFERENCE */
/* this is a more verbose version of the limiting. I wanted to move it
   inside the loop without using gcc specific "inline", so I compacted
   this a bit and copy-pasted wherever needed */
int32
limit_sample( struct limiter *c, int32 s )
{
    int32 val=0;
    int32 a = abs(s);
    int32 sign = (s>0)?1:-1;
    int32 *tbl = c->tbl;
    int32 shift = c->shift;
    
    if( a<=c->threshold )
    {
        return s;
    }
    else
    {
        if( a>c->xmax )
        {
            return sign*c->ymax;
        }
        else
        {
            a = (a-(int32)c->threshold)>>shift;
            val = tbl[a];
            return sign*val;
        }
    }
}
#endif

void
exit_limiter( struct mix_state *s )
{
    masc_rtfree( s->limiter.tbl );
}

void
init_limiter( struct mix_state *s, double db_threshold, int32 max_sample_value )
{
    double r, thr, xmax, ymax;
    int32 width; /* width of table */
    int32 shift, i, tmp;
    int32 *tbl;

    struct limiter *c = &s->limiter;
    
    ymax = c->ymax = max_sample_value;
    c->db_threshold = db_threshold;
    thr  = db_threshold / 20.0;
    c->threshold = thr = max_sample_value * pow( 10, thr );
    r    = (ymax - thr) / (1.0 - 0.70711);
    c->xmax = xmax = thr + 0.70711*r;

    width = xmax - thr;
    /* we have 20+7 bit samples in the buffer, but s->bits_per_sample_and_channel output
       resolution... can reduce size. The "-1" means that we will keep
       one more bit around than we would need... this sub-least-significant-bit
       information can then be encoded in the dithering process if it's turned on */
    c->shift = shift = 20 + MAX_VOL_SHIFT - s->bits_per_sample_and_channel - 1;
    
    width = width >> shift;
    /*     printf("mix: limiter: table width=%d\n", width); */
    
    tbl = masc_rtalloc( width * sizeof(int32) );
    mas_assert( (tbl != NULL), "mix: couldn't allocate limiter curve table; out of memory?" );
        
    for( i=0; i<width; i++ )
    {
        tmp = i<<shift;
        tmp = xmax - tmp - thr;
        tmp = sqrt( r*r - (double)tmp*(double)tmp ) + ymax - r;
        tbl[i] = tmp;
        /* if(tbl[i]>ymax) */
        /*             printf("%d>%f\n", tbl[i],ymax); */
    }
    c->tbl = tbl;
}

void
exit_dither( struct mix_state *s )
{
    masc_rtfree( s->dither.tbl );
}

void
init_dither( struct mix_state *s )
{
    int i, r2, tmp;
    int32 fact;
    struct dither *d = &s->dither;

    d->tbl_size = DITHER_TBL_SIZE;
    d->pos = 0;
    d->tbl = (noise_int_t*)masc_rtalloc( DITHER_TBL_SIZE*sizeof(noise_int_t) );
    mas_assert( (d->tbl != NULL), "mix: couldn't allocate dither noise table; out of memory?" );

    fact = 1 << ( 20 + MAX_VOL_SHIFT - s->bits_per_sample_and_channel );
    
    r2 = RAND_MAX/2;
    
    /* noise 0.5*LSB from a triangular distribution works best
       according to Pohlman book */
    for( i=0; i<DITHER_TBL_SIZE; i++ )
    {
        tmp = 1;
    label:
        if (rand()>r2){
            d->tbl[i] =  (double)fact * (1.0-sqrt((double)rand()/RAND_MAX));
        }
        else {
            d->tbl[i] =  (double)fact * (sqrt((double)rand()/RAND_MAX)-1.0);
        }
        if( d->tbl[i] == 0 )
        {
            if( tmp )
            {
                tmp = 0;
                if(rand()>r2) goto label;
            }
        }        
/*         printf("%d\n", d->tbl[i]); */
    }    
}

static void
choose_fill_func( struct mix_state *state )
{
    if( state->do_soft_limiting )
    {
        if( state->do_dither )
        {
            state->fill_segment = &fill_segment_soft_d;
        }
        else
        {
            state->fill_segment = &fill_segment_soft_nod;
        }
    }
    else
    {
        if( state->do_dither )
        {
            state->fill_segment = &fill_segment_hard_d;
        }
        else
        {
            state->fill_segment = &fill_segment_hard_nod;
        }
    }    
}

static void
choose_mix_func( struct mix_state *s, int sink )
{
    switch( s->sinks[sink]->bits_per_sample_and_channel )
    {
    case 16:
    {
        switch( s->sinks[sink]->channels / s->channels )
        {
        case 1:
            s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int16;
            break;
        case 2:
            s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int16_stom;
            break;
        case 0:
            s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int16_mtos;
            break;
        default: break;
        }
        break;
    }
    case 8:
        if( s->sinks[sink]->format==MAS_LINEAR_FMT )
        {
            switch( s->sinks[sink]->channels / s->channels )
            {
            case 1:
                s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int8;
                break;
            case 2:
                s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int8_stom;
                break;
            case 0:
                s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int8_mtos;
                break;
            default: break;
            }
            break;
        }
        else
        {
            switch( s->sinks[sink]->channels / s->channels )
            {
            case 1:
                s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_uint8;
                break;
            case 2:
                s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_uint8_stom;
                break;
            case 0:
                s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_uint8_mtos;
                break;
            default: break;
            }
        }        
        break;
                
    case 20:
    {
        switch( s->sinks[sink]->channels / s->channels )
        {
        case 1:
            s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int20;
            break;
        case 2:
            s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int20_stom;
            break;
        case 0:
            s->sinks[sink]->circular_buffer_mix = &circular_buffer_mix_int20_mtos;
            break;
        default: break;
        }
        break;
    }
    break;
    }    
}


/* this will give you the corresponding db value, cropped to a [-60..0] intervel, and  multiplied by ten */ 
static int16
get_masdb_from_multiplier( int16 multiplier )
{
    if( multiplier==0 )
    {
        return ( DB_CUTOFF*10 ); /* technically, -Infinity, but since we cut off at -60db in the set routine...*/
    }
    else
    {
        return (200.0 * log10((double)(multiplier)/(double)MAX_VOLUME_MULTIPLIER) );
    }
}
