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

/* 2 OCT 2002 - rocko - NOT reentrant
 * 2 OCT 2002 - rocko - media timestamp clean
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include <math.h>
#include <sfftw.h>
#include <srfftw.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "xstuff.h"

#include "mas/mas_dpi.h"
#include "profile.h"


/* defaults */

#define WIN_WIDTH 500        /* initial values */
#define WIN_HEIGHT 200
#define WIN_LEFT_MARGIN 20

#define SAMPLE_RATE 22050
#define REFRESH 100          /* ms */
#define N 1024               /* please use even number here; power of 2 is great */

#define BUFSIZE 4096

/* for log histogram creation */
#define LO_CUTOFF 22
#define HI_CUTOFF 22050
#define SCALE_CUTOFF -60.0
#define N_BINS 30            /* desired # of bins in the histogram */
#define SPIKES 2             /* whether to draw spikes or boxes; half spike width if so */
#define DO_WORK 0            /* whether to do anything or just pipe the data through */

enum
{
    REDRAW_FULL,             /* erase whole screen and redraw */
    REDRAW_RECTS,            /* erase + redraw individual histogram bins 1 by 1  */
    REDRAW_DIFFERENTIAL      /* draw/erase only the changes from previous screen */
};



struct mas_visual_histogram_info
{
    int    n_intervals;
    int   *lower_bounds;
    int   *centers;
    float *center_freqs;
    float *histo;
    int   *old_coords;
    int    use_width, use_height;
    int    redraw_mode, t;
};


/* defaults that can be changed at runtime */
struct mas_visual_defaults
{
    int   refresh;
    int   n;
    int   n_bins;
    float scale_cutoff;
    int   spikes;
    int   do_work;
    int   experimental_mode;
};


/************************************************************************
 * visual_state
 *
 * State memory structure for this device instance.
 *
 ************************************************************************/
struct visual_state
{
    XInfo xinfo;
    
    int32 reaction;
    int32 data_sink;
    int32 data_source;
    int8  got_source, got_sink;
    int   resolution;

    float *left;
    float *right;
    float *out;
    float *out2;
/*     float *out3; */
    
    uint16 buf_pos;
    uint16 buf_len;

    rfftw_plan plan;

    struct mas_visual_histogram_info *hi;
    struct mas_visual_defaults def;

    int rfr;
    int t_ctr;
    unsigned long color;
    XFontStruct *font8;
    XFontStruct *font10;
    
    float beat_history;
    int beat_detected;
};


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


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

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


/* local prototypes */
void display_image( XInfo *xinfo );
struct mas_visual_histogram_info *mikes_book( int n_intervals );
void make_histogram( struct visual_state *s );
void draw_histogram( struct visual_state *s );






/***************************************************************************
 * 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 visual_state*  state;
    
    /* Allocate state holder and cast it so we can work on it */
    state       = masc_rtalloc(sizeof(struct visual_state));
    if ( state == 0 )
	return mas_error(MERR_MEMORY);

    masd_set_state(device_instance, state); /* set device state */
    memset( state, 0, sizeof (struct visual_state) );
    
    masd_get_port_by_name( device_instance, "sink",
			   &state->data_sink );
    masd_get_port_by_name( device_instance, "source",
			   &state->data_source );
    masd_get_port_by_name( device_instance, "reaction",
			   &state->reaction );

    state->left = masc_rtalloc( BUFSIZE*sizeof(float) );
    state->out = masc_rtalloc( N*sizeof(float) );
    state->out2 = masc_rtalloc( (N+1)*sizeof(float)/2 );
/*     state->out3 = masc_rtalloc( (N+1)*sizeof(float)/2 ); */
    state->buf_pos = 0;
    state->buf_len = 0;
    
    state->plan = rfftw_create_plan( N, FFTW_REAL_TO_COMPLEX, FFTW_ESTIMATE );

    state->def.refresh      = REFRESH;
    state->def.n            = N;
    state->def.n_bins       = N_BINS;
    state->def.scale_cutoff = SCALE_CUTOFF;
    state->def.spikes       = SPIKES;
    state->def.do_work      = DO_WORK;

    state->def.experimental_mode = 0;
    
    state->hi = mikes_book( N_BINS );
    state->hi->redraw_mode = REDRAW_FULL;
    state->hi->use_width  = WIN_WIDTH-2*WIN_LEFT_MARGIN;
    state->hi->use_height = WIN_HEIGHT-2*WIN_LEFT_MARGIN;

    state->hi->t = 0;

    state->beat_detected = 0;
    
    make_truecolor_window( WIN_WIDTH, WIN_HEIGHT, &state->xinfo, DO_WORK );
    
    return 0;
}


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

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

        
    masd_get_state(device_instance, (void**)&state);
    err = masd_get_data_characteristic( *(int32*)predicate, &dc );

    if ( portnum == state->data_sink )
    {
        err = masd_get_data_characteristic( portnum, &dc );
        if ( err < 0 )
            return mas_error(MERR_INVALID);
        
        err = masc_scan_audio_basic_dc( dc, &format, &srate, &resolution, &channels, &endian );
        if ( err < 0 )
            return mas_error(MERR_INVALID);

        state->resolution = resolution;
        state->got_sink = 1;    
        /* source and sink must have same data characteristic. I'm
         * not checking this here! */
    }
    else if ( portnum == state->data_source )
    {
        state->got_source = 1;    
    }
    else
    {
        return mas_error(MERR_NOTDEF);
    }
    
    if ( (state->got_source) && (state->got_sink ) )
    {
        /* schedule our dataflow dependency on data_sink */
        dataflow_port_dependency = masc_rtalloc( sizeof (int32) );
        *dataflow_port_dependency = state->data_sink;
        err = masd_reaction_queue_action(state->reaction, device_instance, 
                                         "mas_visual_convert", 0, 0, 0, 0, 0,
                                         MAS_PRIORITY_DATAFLOW, 1, 1, 
                                         dataflow_port_dependency);
        if ( err < 0 ) return err;
    }

    return 0;
}
int32
mas_dev_disconnect_port( int32 device_instance, void* predicate )
{
    struct visual_state*  state;
    int32 portnum = *(int32*)predicate;

    MASD_GET_STATE(device_instance, state);

    if ( portnum == state->data_sink )
    {
        state->got_sink = FALSE;
    }
    else if ( portnum == state->data_source )
    {
        state->got_source = FALSE;
    }

    return 0;
}

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

int32
mas_dev_exit_instance( int32 device_instance, void* predicate )
{
    struct visual_state*  state;
    
    masd_get_state(device_instance, (void**)&state);

    XUnmapWindow(state->xinfo.display, state->xinfo.window);
    XFlush(state->xinfo.display);
    
    masc_rtfree( state->left );
    masc_rtfree( state->out );
    masc_rtfree( state->out2 );

    /* free the histogram info struct */
    masc_rtfree( state->hi->lower_bounds );
    masc_rtfree( state->hi->centers );
    masc_rtfree( state->hi->center_freqs );
    masc_rtfree( state->hi->histo );
    masc_rtfree( state->hi->old_coords );
                 
    masc_rtfree( state->hi );

    masc_rtfree( state );
    /*     masc_rtfree(state->buffer); */
    
    return 0;
}

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

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


void beat_detection( struct visual_state *s )
{
    int i;
    float total;
    static int rest = 0;
    

    ++rest;


    if( REFRESH * rest > 350 ) /* rest period after detecting beat */
    {
        s->beat_history *= .97; /* decay */
        
        total = 0.0;
        
        for( i=1; i<N/2; i++ )
        {
            total += s->out2[i];
        }
        
        
        total /= (N/2);
        
        total = 20*log10( total );
        if( total < -50 )
            total = -50;
        
        total = total/50 + 1;
        
/*     printf( "%f\n", total ); */
        
        if( total > s->beat_history )
        {
            if( total-s->beat_history > .04 )
                s->beat_detected = 2;
            else
                if( total-s->beat_history > .008 )
                s->beat_detected = 1;

            s->beat_history = total;
            rest = 0;
        }
    }
}



void bs_16( int samples_in_2, int16* raw, unsigned short *sp, XInfo *xinfo )
{
    int i;
/*     unsigned int r1, g1, b1, r2, g2, b2;     */
    unsigned short tmp1, tmp2;
    int width;
/*     static XPoint points[N]; /\*?!*\/ */
    float r;

    /* this could be static */
    static unsigned short tmp3 = (8<<11)|(16<<5)|8;
    

    width = xinfo->wi - 20;
    r = 2.0 * samples_in_2 / (float)width;


/*     for( i=1; i<xinfo->he-1; i++ ) */
/*     { */
/*         for( j=0; j<xinfo->wi; j++ ) */
/*         { */
/*             tmp1 = sp[xinfo->wi*i + j]; */
            
/*             r1 = (tmp1 >> 11); */
/*             g1 = (tmp1 >> 5) & 0x3f; */
/*             b1 = (tmp1     ) & 0x1f; */
            
/*             tmp2 = sp[xinfo->wi*i + j+1]; */
            
/*             r2 = (tmp2 >> 11); */
/*             g2 = (tmp2 >> 5) & 0x3f; */
/*             b2 = (tmp2     ) & 0x1f; */
            
/*             tmp2 = sp[xinfo->wi*i + j-1]; */
            
/*             r2 += (tmp2 >> 11); */
/*             g2 += (tmp2 >> 5) & 0x3f; */
/*             b2 += (tmp2     ) & 0x1f; */
            
/*             tmp2 = sp[xinfo->wi*(i+1) + j]; */
            
/*             r2 += (tmp2 >> 11); */
/*             g2 += (tmp2 >> 5) & 0x3f; */
/*             b2 += (tmp2     ) & 0x1f; */
            
/*             tmp2 = sp[xinfo->wi*(i-1) + j]; */
            
/*             r2 += (tmp2 >> 11); */
/*             g2 += (tmp2 >> 5) & 0x3f; */
/*             b2 += (tmp2     ) & 0x1f; */
            
            
/* /\*                     r1 = r1 << 4; *\/ */
/* /\*                     g1 = g1 << 4; *\/ */
/* /\*                     b1 = b1 << 4; *\/ */
/* /\*                     r2 = r2 << 4; *\/ */
/* /\*                     g2 = g2 << 4; *\/ */
/* /\*                     b2 = b2 << 4; *\/ */
                    
            
/*             r1 = r1 + (r2>>2); */
/*             g1 = g1 + (g2>>2); */
/*             b1 = b1 + (b2>>2); */
            
            
/*             sp[xinfo->wi*i + j] = (r1<<10)|(g1<<4)|(b1>>1); */
            
 
/*                 /\*     sp[xinfo->wi*i + j] = ( sp[xinfo->wi*i + j+1] + *\/ */
/* /\*                                             sp[xinfo->wi*i + j-1] + *\/ */
/* /\*                                             sp[xinfo->wi*(i+1) + j] + *\/ */
/* /\*                                             sp[xinfo->wi*(i-1) + j] ) / 6; *\/ */
/*         } */
/*     } */
    
    for( i=0; i<width; i++ )
    {
        tmp1 = i + 10;
        tmp2 = xinfo->he / 2 +
            (xinfo->he * raw[ (int)(2.0*r*i) ]) / 65536;
        
        sp[xinfo->wi*tmp2 + tmp1] = ~0;                
        if( (sp[xinfo->wi*tmp2 + tmp1+1]>>11)<16 )
            sp[xinfo->wi*tmp2 + tmp1+1] = tmp3;                

        if( (sp[xinfo->wi*tmp2 + tmp1-1]>>11)<16 )
            sp[xinfo->wi*tmp2 + tmp1-1] = tmp3;                

        if( (sp[xinfo->wi*(tmp2+1) + tmp1]>>11)<16 )
            sp[xinfo->wi*(tmp2+1) + tmp1] = tmp3;                

        if( (sp[xinfo->wi*(tmp2-1) + tmp1]>>11)<16 )
            sp[xinfo->wi*(tmp2-1) + tmp1] = tmp3;                        
    }
}


void bs_24( int samples_in_2, int16* raw, unsigned long *lp, XInfo *xinfo )
{
/*     int i,j; */
/*     unsigned int r1, g1, b1, r2, g2, b2;     */
/*     unsigned long tmp1, tmp2; */
/*     int width; */
/* /\*     static XPoint points[N]; /\\*?!*\\/ *\/ */
/*     float r; */

/*     width = xinfo->wi - 20; */
/*     r = 2.0 * samples_in_2 / (float)width; */


/*     for( i=20; i<xinfo->he-20; i++ ) */
/*     { */
/*         for( j=0; j<xinfo->wi; j++ ) */
/*         { */
/*             tmp1 = lp[xinfo->wi*i + j]; */
            
/*             r1 = (tmp1 >> 16) & 0xff; */
/*             g1 = (tmp1 >> 8)  & 0xff; */
/*             b1 = (tmp1     )  & 0xff; */
            
/*             tmp2 = lp[xinfo->wi*i + j+1]; */
            
/*             r2 = (tmp2 >> 16) & 0xff; */
/*             g2 = (tmp2 >> 8) & 0xff; */
/*             b2 = (tmp2     ) & 0xff; */

/*             tmp2 = lp[xinfo->wi*i + j-1]; */
            
/*             r2 += (tmp2 >> 16) & 0xff; */
/*             g2 += (tmp2 >> 8) & 0xff; */
/*             b2 += (tmp2     ) & 0xff;             */
            
/*             tmp2 = lp[xinfo->wi*(i+1) + j]; */
            
/*             r2 += (tmp2 >> 16) & 0xff; */
/*             g2 += (tmp2 >> 8) & 0xff; */
/*             b2 += (tmp2     ) & 0xff; */

/*             tmp2 = lp[xinfo->wi*(i-1) + j]; */
            
/*             r2 += (tmp2 >> 16) & 0xff; */
/*             g2 += (tmp2 >> 8) & 0xff; */
/*             b2 += (tmp2     ) & 0xff; */
            
                        
/*             r1 = (r1 + (r2>>2))>>1; */
/*             g1 = (g1 + (g2>>2))>>1; */
/*             b1 = (b1 + (b2>>2))>>1; */
            
            
/*             lp[xinfo->wi*i + j] = (r1<<16) | (g1<<8) | (b1); */
            
/*         } */
/*     } */
    
/*     for( i=0; i<width; i++ ) */
/*     { */
/*         points[i].x = i + 10; */
/*         points[i].y = xinfo->he / 2 + */
/*             (xinfo->he * raw[ (int)(2.0*r*i) ]) / 65536; */
        
/*         lp[xinfo->wi*points[i].y + points[i].x] = ~0;                 */
/*     } */

/* /\*     for( i=10; i<30; i++ ) *\/ */
/* /\*     { *\/ */
/* /\*         for( j=10; j<30; j++ ) *\/ */
/* /\*         { *\/ */
/* /\*             lp[xinfo->wi*i + j] = ~0; *\/ */
/* /\*         } *\/ */
/* /\*     } *\/ */
    
}



/***************************************************************************
 * mas_visual_convert - read & print contents of sink
 *
 *  predicate: unused
 *
 * Sends mas_data information to stdout.
 *  
 * returns: error
 *
 ***************************************************************************/
int32
mas_visual_convert( int32 device_instance, void* predicate )
{
    struct visual_state*   s;
    struct mas_data*    data;
/*     struct mas_package* package; */

    XInfo *xinfo;
    XEvent my_event;
    unsigned long *lp=0;
    unsigned short *sp=0;
    int i;
    int16 *raw;
    int samples_in_2;
    char stackbuf[32768];
    
/*     static struct timeval time_store[2]; */
    
    int n_refresh;
    
/*     uint8 hlp, iy, cl; */
/*     float tmp; */
    
        
    masd_get_state(device_instance, (void**)&s);
    masd_get_data( s->data_sink, &data );

    if( s->def.do_work )
    {
    xinfo = &s->xinfo;
    n_refresh = SAMPLE_RATE*(s->def.refresh)/1000/N;
    
    /* check for resize events, and maybe expose events if
     * LISTEN_TO_EXPOSE_EVENTS is set in xstuff.h  */
#ifdef LISTEN_TO_EXPOSE_EVENTS
    if( XCheckWindowEvent( xinfo->display, xinfo->window,
                           StructureNotifyMask|ExposureMask, &my_event ) )
#endif        
#ifndef LISTEN_TO_EXPOSE_EVENTS
    if( XCheckWindowEvent( xinfo->display, xinfo->window,
                           StructureNotifyMask, &my_event ) )
#endif
    {
        switch(  my_event.type )
        {
        case ConfigureNotify:
            xinfo->wi = my_event.xconfigure.width;
            xinfo->he = my_event.xconfigure.height;
            
            s->hi->redraw_mode = REDRAW_FULL;
            s->hi->use_width  = xinfo->wi-2*WIN_LEFT_MARGIN;
            s->hi->use_height = xinfo->he-2*WIN_LEFT_MARGIN;
            break;

        case Expose:
/*             if( s->hi->redraw_mode != REDRAW_FULL ) */
            s->hi->redraw_mode = REDRAW_FULL;
            break;

        default:
            break;
        }
    }
    
    

    
    
    switch( xinfo->depth )
    {
    case 24:
        lp = (unsigned long *)xinfo->rgbdata;
        break;

    case 16:
        sp = (unsigned short *)xinfo->rgbdata;
        break;

    default:
        return mas_error( MERR_INVALID );
    }

    if ( s->resolution == 16 )
    {
        samples_in_2 = data->length/8;
        raw = (int16 *)data->segment;
    }
    else
    {
        int32 *tbuf = (int32 *)data->segment;
        samples_in_2 = data->length/16;
        if ( data->length / 2 > sizeof stackbuf )
            return mas_error(MERR_BOUNDS);
        raw = (int16*)stackbuf;

        /* just do a dirty quantization */
        if ( s->resolution == 20 )
        {
            for (i=0; i<samples_in_2*2; i++)
                raw[i] = (int16) (tbuf[i] >> 4);
        }
        else if ( s->resolution == 24 )
        {
            for (i=0; i<samples_in_2*2; i++)
                raw[i] = (int16) (tbuf[i] >> 8);
        }
    }
    


    if( s->buf_pos+s->buf_len+samples_in_2 > BUFSIZE )
    {
        memcpy( s->left, s->left+s->buf_pos, s->buf_len*sizeof(float) );

     /*    s->fft_pos -= s->buf_pos; */
        s->buf_pos = 0;
    }
        
    for( i=0; i<samples_in_2; i++ )
    {
        /*  -1..1 range */
        s->left[s->buf_pos+s->buf_len+i] =
            ( (float)raw[4*i] + (float)raw[4*i+1] )/65536;/* 131072.0
                                                           * + 0.5; */
    }
     
    s->buf_len += samples_in_2;
    
    
    while( s->buf_len > N )
    {

        rfftw_one( s->plan, s->left + s->buf_pos, s->out );
        ++s->rfr;
        
        s->buf_pos += N;
        s->buf_len -= N;

        s->out2[0] += sqrt(s->out[0] * s->out[0]);

        for( i=1; i<(N+1)/2; i++ )
        {
            s->out2[i] += sqrt(s->out[i]*s->out[i] +
                               s->out[N-i]*s->out[N-i]);            
        }
        /* since N is even */
        s->out2[N/2] += sqrt(s->out[N/2] * s->out[N/2]);
    }

    
    if( s->rfr >= n_refresh )
    {

/*         gettimeofday( &time_store[1], (struct timezone *) 0 ); */

/*         printf("total time: %ld us n_refresh %d refresh %d\n", (time_store[1].tv_sec*1000000 + time_store[1].tv_usec) - (time_store[0].tv_sec*1000000 + time_store[0].tv_usec), n_refresh, refresh); */
/*         gettimeofday( &time_store[0], (struct timezone *) 0 ); */



        /* normalize to 1 */
        for( i=1; i<(N+1)/2; i++ )
            s->out2[i] *= 4 / (float)N / (float)s->rfr;


        if( !s->def.experimental_mode )
        {
            make_histogram( s );
            draw_histogram( s );
        }
        else
        {
            /* ARE in experimental mode */

            beat_detection( s );

            /* erase the old */
/*             XSetForeground( xinfo->display, xinfo->gc, 0 ); */
/*             XFillRectangle( xinfo->display, xinfo->window, xinfo->gc, */
/*                             10, 1, */
/*                             width, xinfo->he -2 ); */

            if( xinfo->depth == 16 )
                bs_16( samples_in_2, raw, sp, xinfo );
            else
                bs_24( samples_in_2, raw, lp, xinfo );
            
            display_image( xinfo );
            
            
            /*             XSetForeground( xinfo->display, xinfo->gc, ~0 ); */
/*             XDrawPoints( xinfo->display, xinfo->window, xinfo->gc, */
/*                          points, width, 0 ); */

            
/*             XDrawLines( xinfo->display, xinfo->window, xinfo->gc, */
/*                         points, width, 0 ); */

        }
        
        
        /* ****************************** */
        
        
    /*     for( i=1; i<(N+1)/2; i++ ) */
/*         { */
/*             tmp = s->out2[i]; */
            
/*             /\* heuristics *\/ */
/*             tmp  = 255*sqrt(tmp); */
/*             hlp  = tmp; */
            
            
/*             if( xinfo->depth==24 ) */
/*                 lp[i-1] = (hlp<<16)|(hlp<<8)|hlp; */
/*             else */
/*                 sp[i-1] = ((hlp>>3)<<11)|((hlp>>2)<<5)|(hlp>>3); */
/*         } */
        
/*         for( i=xinfo->he-1; i>0; i-- ) */
/*         { */
/*             for( j=0; j<N/2; j++ ) */
/*             { */
/*                 if( xinfo->depth==24 ) */
/*                     lp[j+i*xinfo->wi] = lp[j+(i-1)*xinfo->wi]; */
/*                 else */
/*                     sp[j+i*xinfo->wi] = sp[j+(i-1)*xinfo->wi]; */
/*             } */
/*         } */
        s->rfr = 0;
        
        
        memset(s->out2, 0, (N+1)*sizeof(float)/2 );
/*         memset(s->out3, 0, (N+1)*sizeof(float)/2 ); */
    }
    
    
                
/*     display_image( xinfo ); */
    
    }
    
    masd_post_data( s->data_source, data );

    return 0;
}


int32
mas_get( int32 device_instance, void* predicate )
{
    struct visual_state*  state;
    int32 err;
    int32 retport;
    char* key;
    struct mas_package arg;
    struct mas_package r_package;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "list", "spikes", "scale_cutoff", "n_bins", "do_work", "" };
    int i, n=0;
    
    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 err;

    /* 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: /*spikes*/
        masc_pushk_int16( &r_package, "spikes", state->def.spikes );
        break;
    case 2: /*scale_cutoff*/
        masc_pushk_int16( &r_package, "scale_cutoff", state->def.scale_cutoff );
        break;
    case 3: /*n_bins*/
        masc_pushk_int16( &r_package, "n_bins", state->def.n_bins );
        break;
    case 4: /*do_work*/
        masc_pushk_int16( &r_package, "do_work", state->def.do_work );
        break;
        
    default:
        break;
    }

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

    return err;
}


int32
mas_set( int32 device_instance, void* predicate )
{
    struct visual_state*  state;
    int32 err;
    char* key;
    struct mas_package arg;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "spikes", "scale_cutoff", "n_bins", "do_work", "" };
    int i, n=0;
    int16 getter;
    
    
    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: /*spikes*/
        masc_pullk_int16( &arg, "spikes", &getter );
        state->def.spikes = getter;
        state->hi->redraw_mode = REDRAW_FULL;
        break;

    case 1: /*scale_cutoff*/
        masc_pullk_int16( &arg, "scale_cutoff", &getter );
        state->def.scale_cutoff = getter;
        state->hi->redraw_mode = REDRAW_FULL;
        break;

    case 2: /*n_bins*/
        masc_pullk_int16( &arg, "n_bins", &getter );
        state->def.n_bins = getter;

        masc_rtfree(state->hi->lower_bounds);
        masc_rtfree(state->hi->centers);
        masc_rtfree(state->hi->center_freqs);
        masc_rtfree(state->hi->histo);
        masc_rtfree(state->hi->old_coords);

        masc_rtfree(state->hi);

        state->hi = mikes_book( state->def.n_bins );
        
        state->hi->redraw_mode = REDRAW_FULL;
        break;

    case 3: /*do_work*/
        masc_pullk_int16( &arg, "do_work", &getter );
        state->def.do_work = getter;
        if( state->def.do_work )
        {
            state->hi->redraw_mode = REDRAW_FULL;
            XMapWindow(state->xinfo.display, state->xinfo.window);
            XFlush(state->xinfo.display);
        }
        else
        {
            XUnmapWindow(state->xinfo.display, state->xinfo.window);
            XFlush(state->xinfo.display);
        }
        
        
        break;

    default:
        break;
    }

    /* cleanup after our mess */
    err = masd_set_post( key, &arg );

    
    return err;
}



void make_histogram( struct visual_state *s )
{
    int i, j;
    
    /* convenience */
    int n;
    int *l;
    float *h;
    float tmp;
    float scale_cutoff;
    
        
    n = s->hi->n_intervals;
    l = s->hi->lower_bounds;
    h = s->hi->histo;
    scale_cutoff = s->def.scale_cutoff;
    
    
    /* null out */
    /* memset( h, 0, n * sizeof(unsigned int) ); */

    for( i=0; i<n; i++ )
    {
        tmp=0;
        for( j=l[i]; j<l[i+1]; j++ )
        {
            tmp += s->out2[j];
        }
        tmp /= (l[i+1] - l[i]);

        /* want log scale */
        tmp = 20*log10( tmp );
        if( tmp < scale_cutoff )
            tmp = scale_cutoff;

        h[i] = -tmp/scale_cutoff + 1;
    }    
}


/* assign intervals that will coincide with the ones
 * in Mike's "Lab Manual" book for the cases covered there
 * (1-octave and 3-octave intervals), compute otherwise */ 
struct mas_visual_histogram_info *
mikes_book( int n_intervals )
{
    const int octave_centers[] = {31,63,125,250,500,1000,2000,4000,8000,16000};

    const int octave_lower[]={22,44,88,176,353,707,1414,2825,5650,11300,22500};

    const float octave_center_freqs[] = {31.5, 63,125,250,500,1000,2000,
                                         4000,8000,16000};

    const int third_centers[] = {25,31,40,50,63,80,100,125,160,200,250,
                                315,400,500,630,800,1000,1250,1600,2000,
                                2500,3150,4000,5000,6300,8000,10000,
                                12500,16000,20000};

    const int third_lower[] = {22,28,35,44,57,71,88,113,141,176,225,283,353,
                               440,565,707,880,1130,1414,1760,2250,2825,3530,
                               4400,5650,7070,8800,11300,14140,17600,22500};

    const float third_center_freqs[] = {25,31.5,40,50,63,80,100,125,160,200,
                                        250,315,400,500,630,800,1000,1250,1600,
                                        2000,2500,3150,4000,5000,6300,8000,
                                        10000,12500,16000,20000};


    struct mas_visual_histogram_info *hi;
    double log_width, center, low;
    int i;
    int reached_nyquist;
    int keepgoing;

    
    hi = masc_rtalloc( sizeof(struct mas_visual_histogram_info) );

    hi->n_intervals = n_intervals;

    hi->lower_bounds = masc_rtalloc( n_intervals * sizeof(int) );
    hi->centers      = masc_rtalloc( n_intervals * sizeof(int) );
    hi->center_freqs = masc_rtalloc( n_intervals * sizeof(float) );

    log_width = ( log(HI_CUTOFF) - log(LO_CUTOFF) ) / n_intervals;

    reached_nyquist = n_intervals;
    for( i=n_intervals-1; i>=0; i-- )
    {
        switch( n_intervals )
        {
        case 10:
            /* use octave band values from book */
            center = octave_centers[i];
            hi->center_freqs[i] = octave_center_freqs[i];
            low = octave_lower[i];
            break;
            
        case 30:
            /* use third octave band values from book */
            center = third_centers[i];
            hi->center_freqs[i] = third_center_freqs[i];
            low = third_lower[i];
            break;
            
        default:
            /* compute */
            center = exp( ( i+0.5 ) * log_width + log(LO_CUTOFF) );
            low    = exp(   i       * log_width + log(LO_CUTOFF) );
            hi->center_freqs[i] = (float)( (int)center );
        }
        
        if( center >= SAMPLE_RATE/2 )
            reached_nyquist = i;
        
        
        /* express these frequencies as indices into the fft array */
        center *= (float)N/(float)SAMPLE_RATE;
        low    *= (float)N/(float)SAMPLE_RATE;
        
        hi->centers[i]      =  center;
        hi->lower_bounds[i] =  low;
    }
    

    /* limit top to sensible range */
    hi->n_intervals = reached_nyquist;

    /* (don't go above Nyquist freq) */
    if( hi->lower_bounds[hi->n_intervals] > (N/2) )
        hi->lower_bounds[hi->n_intervals] = N/2;
    
    
    /* low frequency bins may still be nonsense if you
       choose N too small / n_intervals too big! so cut them off!*/
    keepgoing=1;
    while( keepgoing )
    {
        keepgoing=0;
        for( i=0; i<hi->n_intervals; i++ )
        {
            if( hi->lower_bounds[i+1] == hi->lower_bounds[i] )
            {
                hi->n_intervals = hi->n_intervals - 1;

                memmove( hi->lower_bounds, &(hi->lower_bounds[1]),
                         (hi->n_intervals-i) * sizeof(int) );
                memmove( hi->centers, &(hi->centers[1]),
                         (hi->n_intervals-i) * sizeof(int) );
                memmove( hi->center_freqs, &(hi->center_freqs[1]),
                         (hi->n_intervals-i) * sizeof(float) );
                
                keepgoing = 1;
                break;
            }
        }
/*         keepgoing=0; */
    }
    
    hi->histo = masc_rtalloc( hi->n_intervals * sizeof(float) );
    hi->old_coords = masc_rtalloc( hi->n_intervals * sizeof(int) );

/*     printf("intervals: %d\n", hi->n_intervals); */
    
/*     for( i=0; i< hi->n_intervals; i++ ) */
/*     { */
/*         printf("%d %f\n", hi->lower_bounds[i], hi->center_freqs[i] ); */
/*     } */
    
    return hi;
}



void draw_histogram( struct visual_state *s )
{
    int i;
    XInfo *xinfo;
    int use_height, use_width;
    int n_intervals;
    float x, y, w, h;
    char a[32];
    int spikes;
    unsigned char r, g, b;
    float r_f, g_f, b_f;
    
    int tw;
    
    /*some commonly used values */
    xinfo = &s->xinfo;
    n_intervals = s->hi->n_intervals;
    use_width   = s->hi->use_width;
    use_height  = s->hi->use_height;
    spikes = s->def.spikes;

    
    /* "subtle" color change :) */
    ++s->t_ctr;
    if( !(s->t_ctr%9) )
    {
        ++s->hi->t;
        s->hi->t = s->hi->t % 768;

        s->hi->redraw_mode=REDRAW_RECTS;        
        
        r = g = b = 0;
        if( s->hi->t<256 )
        {
            /* go b->g */
            g = s->hi->t-512;
            b = 255 - g;
        }
        else
            if( s->hi->t<512 )
            {
                /* go g->r */
                r = s->hi->t;
                g = 255 - r;
            }
            else
            {
                /* go r->b */
                b = s->hi->t-256;
                r = 255 - b;
            }
        
        /* brighten */
        r_f = (float)(255 - r);
        g_f = (float)(255 - g);
        b_f = (float)(255 - b);

        r = (float)r + r_f * 0.7;
        g = (float)g + g_f * 0.7;
        b = (float)b + b_f * 0.7;

        if( xinfo->depth==24 )
        {
            s->color = (r<<16)|(g<<8)|b;
        }
        else
        {
            s->color = (r>>3)<<11|(g>>2)<<5|(b>>3);
        }        
    }
    


    
    switch( s->hi->redraw_mode )
    {
    case REDRAW_RECTS:
        for( i=0; i<n_intervals; i++ )
        {
            if( spikes )
                x =  ((float)i+0.5)*(float)use_width /
                    (float)n_intervals+WIN_LEFT_MARGIN-spikes;
            else
                x =  i*use_width / n_intervals+WIN_LEFT_MARGIN;
            
            y = use_height+WIN_LEFT_MARGIN- use_height *
                s->hi->histo[i];
            
            if( spikes )
                w = ((float)i+0.5)*(float)use_width/
                    (float)n_intervals+WIN_LEFT_MARGIN + spikes;
            else
                w = (i+1)*use_width/ n_intervals+WIN_LEFT_MARGIN-2;
            
            h = use_height+WIN_LEFT_MARGIN;
            
            w = w-x;
            h = h-y;
            
            
            XSetForeground( xinfo->display, xinfo->gc, 0 );
            XFillRectangle( xinfo->display, xinfo->window, xinfo->gc,
                            x-1, WIN_LEFT_MARGIN,
                            w+1, use_height );
            
            XSetForeground( xinfo->display, xinfo->gc, s->color );
            
            
            XFillRectangle( xinfo->display, xinfo->window, xinfo->gc,
                            x, y, w, h );
            
            s->hi->old_coords[i] = h;
        }

        s->hi->redraw_mode = REDRAW_DIFFERENTIAL;
        break;

        
    case REDRAW_FULL:
        XSetForeground( xinfo->display, xinfo->gc, 0 );
        XFillRectangle( xinfo->display, xinfo->window, xinfo->gc,
                        0, 0, xinfo->wi, xinfo->he );
        
        XSetForeground( xinfo->display, xinfo->gc, s->color );    
        
        for( i=0; i<n_intervals; i++ )
        {
            if( spikes )
                x =  ((float)i+0.5)*(float)use_width /
                    (float)n_intervals+WIN_LEFT_MARGIN-spikes;
            else
                x =  i*use_width / n_intervals+WIN_LEFT_MARGIN;
            
            y = use_height+WIN_LEFT_MARGIN- use_height *
                s->hi->histo[i];
            
            if( spikes )
                w = ((float)i+0.5)*(float)use_width/
                    (float)n_intervals+WIN_LEFT_MARGIN + spikes;
            else
                w = (i+1)*use_width/ n_intervals+WIN_LEFT_MARGIN-2;
            
            h = use_height+WIN_LEFT_MARGIN;
            
            w = w-x;
            h = h-y;
            
            
            XFillRectangle( xinfo->display, xinfo->window, xinfo->gc,
                            x, y, w, h );
            
            s->hi->old_coords[i] = h;
            
            if( s->hi->center_freqs[i]<1000 )
            {
                if( s->hi->center_freqs[i]==(int)s->hi->center_freqs[i] )
                {
                    sprintf( a, "%d", (int)s->hi->center_freqs[i] );
                }
                else
                {
                    sprintf( a, "%.1f", s->hi->center_freqs[i] );
                }
            }
            else
                sprintf( a, "%.1fk", s->hi->center_freqs[i]/1000.0 );
            
            if( !s->font8 ){
                s->font8 = XLoadQueryFont( xinfo->display,
                                       "-adobe-helvetica-medium-r-*-*-8-*-*-*-*-*-*-*");
                s->font10 = XLoadQueryFont( xinfo->display,
                                       "-adobe-helvetica-medium-r-*-*-10-*-*-*-*-*-*-*");
            }
            

            /* some heuristics */
            if( xinfo->wi/n_intervals > 22 )
            {
                XSetFont( xinfo->display, xinfo->gc, s->font10->fid );
                tw = XTextWidth( s->font10, a, strlen(a) );
            }
            else
            {
                XSetFont( xinfo->display, xinfo->gc, s->font8->fid );
                tw = XTextWidth( s->font8, a, strlen(a) );
            }
            
            
            if( xinfo->wi/n_intervals > 16 )
            {
                XSetForeground( xinfo->display, xinfo->gc, ~0 );
                if( spikes )
                    XDrawString( xinfo->display, xinfo->window, xinfo->gc,
                                 x-tw/2, xinfo->he-WIN_LEFT_MARGIN+12,
                                 a, strlen(a) );
                else
                    XDrawString( xinfo->display, xinfo->window, xinfo->gc,
                                 x+use_width/n_intervals/2-tw/2,
                                 xinfo->he-WIN_LEFT_MARGIN+12, a, strlen(a) );
                
                XSetForeground( xinfo->display, xinfo->gc, s->color );
            }
        }
        s->hi->redraw_mode = REDRAW_DIFFERENTIAL;
        break;
        
        
    case REDRAW_DIFFERENTIAL:
        for( i=0; i<n_intervals; i++ )
        {
            if( spikes )
                x =  ((float)i+0.5)*(float)use_width /
                    (float)n_intervals+WIN_LEFT_MARGIN-spikes;
            else
                x =  i*use_width / n_intervals+WIN_LEFT_MARGIN;
            
            y = use_height+WIN_LEFT_MARGIN- use_height *
                s->hi->histo[i];
            
            
            if( spikes )
                w = ((float)i+0.5)*(float)use_width/
                    (float)n_intervals+WIN_LEFT_MARGIN + spikes;
            else
                w = (i+1)*use_width/ n_intervals+WIN_LEFT_MARGIN-2;

            
            h = use_height+WIN_LEFT_MARGIN;
            
            w = w-x;
            h = h-y;

            tw = s->hi->old_coords[i] - (int)h;
            s->hi->old_coords[i] = h;
            
            if( tw<0 )
            {
                XSetForeground( xinfo->display, xinfo->gc, s->color );
                XFillRectangle( xinfo->display, xinfo->window, xinfo->gc,
                                x, y, w, -tw+1);
            }
            else
            {
                if( tw>0 )
                {
                    XSetForeground( xinfo->display, xinfo->gc, 0 );
                    XFillRectangle( xinfo->display, xinfo->window, xinfo->gc,
                                    x, y-tw-1, w, tw+1 );
                }
            }
        }

    default:
        break;
    }
    
    
    
    XFlush(xinfo->display);


 
/* #ifdef SH_MEM */
/*     /\* gettimeofday( &time_store[0], (struct timezone *) 0 ); *\/ */
    
/*     XShmPutImage( xinfo->display, xinfo->window, xinfo->gc, */
/*                   xinfo->ximage, 0, 0, 0, 0, */
/*                   xinfo->ximage->width, */
/*                   xinfo->ximage->height, */
/*                   False ); */
/*     XFlush(xinfo->display); */
/* /\*             gettimeofday( &time_store[1], (struct timezone *) 0 ); *\/ */
/* /\*             printf("total time: %ld us\n", (time_store[1].tv_sec*1000000 + time_store[1].tv_usec) - (time_store[0].tv_sec*1000000 + time_store[0].tv_usec)); *\/ */
/* #endif */
/* #ifndef SH_MEM */
/*     XPutImage(xinfo->display, xinfo->window, xinfo->gc, xinfo->ximage, */
/*               0, 0, 0, 0, */
/*               xinfo->ximage->width, */
/*               xinfo->ximage->height); */
/* #endif */

    
        
    
    return;
}




void display_image( XInfo *xinfo )
{
#ifdef SH_MEM
    /* gettimeofday( &time_store[0], (struct timezone *) 0 ); */
    
    XShmPutImage( xinfo->display, xinfo->window, xinfo->gc,
                  xinfo->ximage, 0, 0, 0, 0,
                  xinfo->ximage->width,
                  xinfo->ximage->height,
                  False );
    XFlush(xinfo->display);
/*             gettimeofday( &time_store[1], (struct timezone *) 0 ); */
/*             printf("total time: %ld us\n", (time_store[1].tv_sec*1000000 + time_store[1].tv_usec) - (time_store[0].tv_sec*1000000 + time_store[0].tv_usec)); */
#endif
#ifndef SH_MEM
    XPutImage(xinfo->display, xinfo->window, xinfo->gc, xinfo->ximage,
              0, 0, 0, 0,
              xinfo->ximage->width,
              xinfo->ximage->height);
#endif

    
        
        
    return;
}
