view tools/acgram.c @ 0:5242703e91d3 tip

Initial checkin for AIM92 aimR8.2 (last updated May 1997).
author tomwalters
date Fri, 20 May 2011 15:19:45 +0100
parents
children
line wrap: on
line source
/*
 acgram.c       Autocorrelogram program by Michael Allerhand.
----------

  Short-time autocorrelation function applied to each row of input frames.
  Output frames consist of row-wise autocorrelation coefficients.
  The routine acf() uses an fft to compute the array of lagged products
  which is the autocovariance function. An optional normalization (dividing
  each coefficient by the zeroth coefficient) produces the autocorrelation
  function, (and this is then scaled up to the range [0-5000]).

Input/output:
  Read a header and frame in NAP format.
  Write a header, (constructed from the original input header), and a
  succession of frames in SAI format.

  The input file is interpreted as one large frame in NAP format to be
  divided into input frames, each to be processed and output in SAI format.

  The options "start" and "length" specify the input file.
  The special option value length=max specifies all the input file from
  the given start to its end.

  The options "width" and "frstep"  specify the input frames.
  The width option is the framewidth of each input frame) and the frstep
  option is the frameshift between input frames in the input file.
  The special option value width=max specifies the input framewidth as equal
  to the given input file length, (and if this is also "max", then the
  input framewidth is the remainder of the input file).

  Most options in the input header are copied to the output header.
  This enables options which are needed for the eventual display
  to pass straight through. Some options are set so that they can override
  the input header. For example, the display option is set on to enable
  display even when input has display=off. The animate option can be set on
  even when the input has animate=off.
  Parts of the header are changed for the new sai format:
  (frames, frameshift, framewidth, frameheight, framebytes).

Padding:
  The output framewidth is the given maximum acf lag, which is also the
  number of acf coefficients computed from each row of each input frame.
  The rows of each input frame are padded with zeroes so that the input
  framewidth is at least twice the required output framewidth (max acf lag),
  and also a power of 2. This is because the fft method is "radix-2", and
  because the output acf is symmetrical so that just half the points are
  unique. It is advisable to pad input frames for the additional reason that
  padding counters end-effects (which can be significant with the fft method
  for computing acf).

  Therefore each row of each input frame is padded with zeroes to the next
  power of 2 larger than either the original input framewidth or twice the
  max acf lag (whichever is  the larger).
  If necessary, extra padding can be enforced using the padding option to
  add extra zeroes, padding to a larger power of 2.
  The amount of extra padding is "exponential" since if the basic padded
  framesize is N, and the padding option is n, then extra padding is to:
  N * 2**n.
  (n=0 by default, so that N is not changed. When n=1 then N is doubled, and
  when n=2 then N is quadrupled, etc.).


Examples:

1. Autocorrelogram of a NAP, animated and normalized, with max lag 16ms:

gennap len=128ms output=stdout display=off file | acgram lag=16ms norm=on anim=on > file.sai
gensai useprev=on headr=5 top=1000 file             -(for landscape plot)
genspl useprev=on headr=5 top=1000 pensize=2 file   -(for spiral plot)

2. Autocorrelogram of an SAI:
  (Note that gensai removes file.sai, so you must use some other name, eg foo.sai).

gensai len=64 pwidth=64 nwidth=0 output=stdout display=off file |  \
saitonap frame=3 | acgram lag=32ms frame=1 > foo.sai
gensai useprev=on top=1000 headr=5 mag=2 foo

3. Autocorrelogram of a NAP, followed by a cross-channel summary:

gennap len=128ms output=stdout display=off file | acgram lag=16ms scale=0.001 > file.sai
edframe frame=1 file.sai | integframe var=freq > file.sum

*/


#include <stdio.h>
#include <math.h>
#include "header.h"
#include "options.h"
#include "units.h"
#include "strmatch.h"
#include "sigproc.h"

char applic[]     = "Autocorrelogram auditory image." ;

static char *helpstr  , *debugstr, *dispstr , *anistr   , *startstr  ;
static char *lengthstr, *widthstr, *shiftstr, *headerstr, *framestr  ;
static char *normstr  , *wstr    , *scalestr, *lagstr   , *padstr    ;
static char *negstr   , *vstr ;

static Options option[] = {
    {   "help"      ,   "off"       ,  &helpstr     ,   "help"                                       , DEBUG   },
    {   "debug"     ,   "off"       ,  &debugstr    ,   "debugging switch"                           , DEBUG   },
    {   "display"   ,   "on"        ,  &dispstr     ,   "Display output image."                      , SETFLAG },
    {   "animate"   ,   "off"       ,  &anistr      ,   "Animate cartoon."                           , SETFLAG },
    {   "frames"    ,   "1-max"     ,  &framestr    ,   "Select frames inclusively"                  , VAL     },
    {   "Header"    ,   "on"        ,  &headerstr   ,   "Header (for gensai useprevious=on)."        , VAL     },
    {   "start"     ,   "0"         ,  &startstr    ,   "Start point in i/p file."                   , VAL     },
    {   "length"    ,   "max"       ,  &lengthstr   ,   "Length of i/p file to process."             , VAL     },
    {   "frstep"    ,   "16ms"      ,  &shiftstr    ,   "Step between input frames."                 , VAL     },
    {   "width"     ,   "32ms"      ,  &widthstr    ,   "Width of input frames."                     , VAL     },
    {   "lag"       ,   "32ms"      ,  &lagstr      ,   "Maximum acf lag (output frame width)."      , VAL     },
    {   "padding"   ,   "0"         ,  &padstr      ,   "Exponential frame padding"                  , SVAL    },
    {   "window"    ,   "on"        ,  &wstr        ,   "Hamming window"                             , SETFLAG },
    {   "normalize" ,   "off"       ,  &normstr     ,   "Normalization"                              , SETFLAG },
    {   "scale"     ,   "1."        ,  &scalestr    ,   "Scale factor for output"                    , VAL     },
    {   "negative"  ,   "off"       ,  &negstr      ,   "write negative half of acf (zeroth lag on right)", SVAL    },
    {   "verbose"   ,   "off"       ,  &vstr        ,   "print at each frame"                        , SVAL    },
   ( char * ) 0 } ;


int     samplerate ;

int     frameheight, framewidth ;
int     frames, framebytes ;
int     frameshift ;

int     Newframeheight, Newframewidth ;
int     Newframes, Newframebytes ;
int     Newframeshift ;

int     start, length ; /* Size of input file (num cols) */
int     frameslength ;  /* Length (num cols) of input file to process */

int     zeroes ;
int     window ;        /* Flag for Hamming window */
float   scale  ;        /* Scale factor for output */
int     normalize ;     /* Flag to normalize acf   */
int     negative  ;
int     Verbose   ;

short  *file ;          /* Input file (NAP format) */
float  *W    ;          /* Hamming window for fft  */
float  *buf  ;          /* fft working space       */


main(argc, argv)
int   argc ;
char *argv[] ;
{
    FILE   *fp ;
    char   *header, *SaiHeader();
    char   *versionstr, c ;
    int     startbytes, framebyteshift ;
    short  *frame, *endframe ;
    int     i, a, b ;
    float   acgram(), f, newscale = 1. ;

    fp = openopts( option,argc,argv ) ;
    if ( !isoff( helpstr ) )
	helpopts( helpstr, argv[0], applic, option ) ;

    window    = ison( wstr    ) ;
    normalize = ison( normstr ) ;
    negative  = ison( negstr  ) ;
    Verbose   = ison( vstr    ) ;


    /**** parse bounds on number of frames ****/

    if ( selector( framestr, &a, &b ) == 0 ) {
	fprintf(stderr,"acgram: bad frame selector [%s]\n", framestr ) ;
	exit( 1 ) ;
    }


    /**** Input frame dimensions (from header and options) ****/

    if ((header = ReadHeader(fp)) == (char *) 0 ) {
	fprintf(stderr,"acgram: header not found\n");
	exit(1);
    }

    if ( (versionstr = HeaderString( header, "Version" )) == (char *)0 ) {
	fprintf(stderr,"acgram: model version-number not found in header\n");
	exit(1);
    }

    samplerate  = HeaderSamplerate( header );

    /* In NAP format, the size of the 2-D image is given by:  */
    /*   frameheight = number of rows (filterbank channels)   */
    /*   frames      = number of columns (sample points)      */

    frameheight = HeaderInt( header, "frameheight" );
    frames      = HeaderInt( header, "frames"      );

    if ( ( frameshift  = to_p( shiftstr, samplerate ) ) <= 0 ) {
	fprintf(stderr,"acgram: non-positive frstep [%d]\n", frameshift);
	exit(1);
    }

    /* calculate start of first frame (in cols) */

    if ( ( start = to_p( startstr, samplerate ) + ( a-1 ) * frameshift ) >= frames ) {
	fprintf(stderr,"acgram: input file too small (%dms) for given framing parameters\n", frames * 1000 / samplerate );
	exit(1);
    }

    /* get length of input file (in cols) and framewidth of input frames */

    if ( ismax( lengthstr ) ) length = frames - start ;
    else                      length = to_p( lengthstr, samplerate ) - start ;

    if ( ismax( widthstr ) )  framewidth = length ;
    else                      framewidth = to_p( widthstr, samplerate ) ;

    if ( length < framewidth ) {
	fprintf(stderr,"acgram: input file too small (%dms) for given framing parameters\n", frames * 1000 / samplerate );
	exit(1);
    }

    /* calculate length (num cols) to process, and the number of frames */

    if ( b==0 ) {
	frameslength = length ;
	Newframes = 1 + ( length - framewidth ) / frameshift ;
    }
    else {
	frameslength = framewidth + ( b-a ) * frameshift ;
	Newframes = b - ( a-1 ) ;
    }

    if ( start + frameslength > frames ) {
	fprintf(stderr,"acgram: input file too small (%dms) for requested start and length \n", frames * 1000 / samplerate );
	exit(1);
    }

    framebytes = frameheight * frameslength * sizeof(short) ;
    startbytes = frameheight * start  * sizeof(short) ;


    /**** Output frame dimensions ****/

    Newframeheight =  frameheight ;
    Newframewidth  =  to_p( lagstr, samplerate );   /* Max acf lag   */
    Newframeshift  =  frameshift ;
    Newframebytes  =  Newframeheight * Newframewidth * sizeof(short) ;


    /**** Padding and output scale factor ****/

    if ( ( Newframewidth << 1 ) < framewidth )
	zeroes = ( getpower( framewidth ) << atoi( padstr ) ) - framewidth ;
    else if ( Newframewidth <= framewidth )
	zeroes = ( getpower( Newframewidth << 1 ) << atoi( padstr ) ) - framewidth ;
    else {
	fprintf(stderr,"acgram: required max lag [%dms] greater than framewidth [%dms]\n", Newframewidth*1000/samplerate, framewidth*1000/samplerate);
	exit( 1 ) ;
    }

    if ( normalize == 0 )   /* no normalize, so divide by length to get average of products */
	scale = 1./(double)(framewidth + zeroes) * atof( scalestr ) ;
    else
	scale = atof( scalestr ) ;


    /**** Debug ****/

    if ( ison( debugstr ) ) {
	fprintf(stderr, "Input:   frames=%d frameheight=%d frameshift=%d\n", frames, frameheight, frameshift ) ;
	fprintf(stderr, "Sizes:   start=%d length=%d framewidth=%d frameslength=%d\n", start, length, framewidth, frameslength ) ;
	fprintf(stderr, "Output:  zeroes=%d Newframewidth=%d Newframes=%d \n", zeroes, Newframewidth, Newframes ) ;
	exit( 1 ) ;
    }


    /**** Allocate space (input file, window coeffs, and fft working space) ****/

    if ( ( file = (short *)malloc( framebytes ) ) == NULL ) {
	fprintf(stderr,"acgram: malloc out of space\n");
	exit(1);
    }

    if ( window )
	W = hamming( framewidth ) ;

    buf = (float *)malloc( ( framewidth + zeroes ) * sizeof(float) );


    /**** Write new sai header ****/

    if ( ison( headerstr ) )
	WriteHeader( SaiHeader(header), stdout );


    /**** Seek to start of input file in blocks of framebytes ****/

    for (i=framebytes ; i < startbytes ; i += framebytes)
	if ( fread( file, framebytes, 1, fp ) == NULL ) {
	    fprintf(stderr,"acgram: missing data after header\n");
	    exit(1);
	}
    if ( (startbytes -= (i - framebytes)) > 0 )
	if ( fread( file, startbytes, 1, fp ) == NULL ) {
	    fprintf(stderr,"acgram: missing data after header\n");
	    exit(1);
	}


    /**** Read framebytes of i/p file ****/

    if ( fread( file, framebytes, 1, fp ) == NULL ) {
	fprintf(stderr,"acgram: missing data after header\n");
	exit(1);
    }


    /**** Process frames ****/

    framebyteshift = frameshift * frameheight ;
    endframe = file + Newframes * framebyteshift ;

    for ( frame = file, i=1 ; frame < endframe ; frame += framebyteshift, i++ ) {
	if ( Verbose )
	    fprintf(stderr,"acgram: %d / %d  [%dms]\n", i, Newframes, start*1000/samplerate );
	if ( ( f = acgram( frame ) ) < newscale )
	    newscale = f ;
	start += frameshift ;
    }

    fclose(fp);

    if ( newscale < 1. ) {
	fprintf( stderr, "Warning: 16-bit overflow during acgram.  " ) ;
	if ( normalize == 0 )
	    fprintf( stderr, "Try scale<%f\n", newscale * (float)(framewidth + zeroes) ) ;
	else
	    fprintf( stderr, "Try scale<%f\n", newscale ) ;
    }

    if ( Verbose )
	fprintf(stderr,"acgram: done\n" ) ;
}


/************************** Autocorrelation function ***********************/

/* Call acf for each row in the SAI frame */

float acgram( frame )
short *frame ;
{
    register int  i, j, row, col ;
    float    writebuf(), f, newscale = 1. ;

    for ( row=0 ; row < frameheight ; row++ ) {

	if ( window )
	    for ( col=0 , i=row ; col < framewidth ; col++, i+=frameheight )
		buf[col] = frame[i] * W[col] ;
	else
	    for ( col=0 , i=row ; col < framewidth ; col++, i+=frameheight )
		buf[col] = frame[i] ;

	for ( j=0 ; j<zeroes ; j++ )    /* padding */
	    buf[col++] = 0 ;

	acf( buf, framewidth+zeroes ) ;

	if ( normalize == 0 )
	    f = writebuf( buf, Newframewidth, scale ) ;
	else
	    f = writebuf( buf, Newframewidth, scale/buf[0] ) ;

	if ( f < newscale )
	    newscale = f ;
    }

    return ( newscale ) ;
}


/********************** Write o/p ****************************************/

float writebuf( buf, n, scale )       /* write buffer as scaled shorts */
float *buf ;
int    n ;
float  scale ;
{
    register int  i ;
    short         p ;
    float newscale  ;

    if ( !negative ) {  /* usual acf (ie. positive half, with zeroth lag on left) */
	for (i=0 ; i < n ; i++)  {

	    newscale = check_overflow( buf[i], scale, 1 ) ;

	    p = (short)( buf[i] * scale ) ;
	    fwrite( &p, sizeof(short), 1, stdout ) ;
	}
    }

    else {      /* write negative half of acf (zeroth lag on right) */
	for ( i = n-1 ; i >= 0 ; --i )  {

	    newscale = check_overflow( buf[i], scale, 1 ) ;

	    p = (short)( buf[i] * scale ) ;
	    fwrite( &p, sizeof(short), 1, stdout ) ;
	}
    }
    return ( newscale ) ;
}



/************************ Output header ************************************/
/*
   First create a new header with
     nwidth_aid
     pwidth_aid
     frstep_aid
   Then copy the original nap header into the sai header, changing in order:
     frames
     frameshift
     framewidth
     frameheight
     framebytes
     animate
     display
   Then change applic name [gennap] to [gensai] in the Version string.
   Finally, update the new header_bytes, and return the new header.
*/

char *SaiHeader( napheader )
char *napheader ;
{
    char *saiheader;
    char *p0, *p1, *p2, *s, str[64];

    saiheader = (char *)malloc( strlen(napheader) + 128 ) ;

    p0 = saiheader ;
    p1 = napheader ;


    /** copy initial header bytes **/

    p2 = HeaderString( napheader , "header_bytes" ) ;
    while( p1 < p2 )
	*p0++ = *p1++ ;
    while (*p1 != '\n')
	*p0++ = *p1++ ;
    *p0++ = *p1++ ;


    /** insert nwidth_aid at top of new header **/

    sprintf(str,"nwidth_aid=0\n");
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;


    /** insert pwidth_aid into new header **/

    sprintf(str,"pwidth_aid=%dms\n", (int)(1000*((float)Newframewidth/samplerate)) );
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;


    /** insert frstep_aid into new header **/

    sprintf(str,"frstep_aid=%dms\n", (int)(1000*((float)frameshift/samplerate)) );
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;


    /** copy up to frames **/

    p2 = HeaderString( napheader , "frames" ) ;
    while( p1 < p2 )
	*p0++ = *p1++ ;

    sprintf(str,"%d\n", Newframes);
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;
    while (*p1 != '\n')
	*p1++;
    *p1++;


    /** copy up to frameshift **/

    p2 = HeaderString( napheader , "frameshift" ) ;
    while ( p1 < p2 )
	*p0++ = *p1++ ;

    sprintf(str,"%d\n", Newframeshift);
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;
    while (*p1 != '\n')
	*p1++;
    *p1++;


    /** copy up to framewidth **/

    p2 = HeaderString( napheader , "framewidth" ) ;
    while ( p1 < p2 )
	*p0++ = *p1++ ;

    sprintf(str,"%d\n", Newframewidth);
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;
    while (*p1 != '\n')
	*p1++;
    *p1++;


    /** copy up to frameheight **/

    p2 = HeaderString( napheader , "frameheight" ) ;
    while ( p1 < p2 )
	*p0++ = *p1++ ;

    sprintf(str,"%d\n", Newframeheight);
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;
    while (*p1 != '\n')
	*p1++;
    *p1++;


    /** copy up to framebytes **/

    p2 = HeaderString( napheader , "framebytes" ) ;
    while ( p1 < p2 )
	*p0++ = *p1++ ;

    sprintf(str,"%d\n", Newframebytes);
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;
    while (*p1 != '\n')
	*p1++;
    *p1++;


    /** copy up to animate_ctn **/

    p2 = HeaderString( napheader , "animate_ctn" ) ;
    while ( p1 < p2 )
	*p0++ = *p1++ ;

    sprintf(str,"%s\n", anistr );
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;
    while (*p1 != '\n')
	*p1++;
    *p1++;


    /** copy up to display **/

    p2 = HeaderString( napheader , "display" ) ;
    while ( p1 < p2 )
	*p0++ = *p1++ ;

    sprintf(str,"%s\n", dispstr );
    for (s = str ; *s != '\n' ; )
	*p0++ = *s++;
    *p0++ = *s;
    while (*p1 != '\n')
	*p1++;
    *p1++;


    /** copy rest of header **/

    p2 = HeaderString( napheader , "Version" ) ;
    while ( p1 < p2 )
	*p0++ = *p1++ ;

    while (*p1 != ']')          /* change applic name [gennap] to [gensai] */
	*p0++ = *p1++ ;
    *(p0-3) = 's' ;
    *(p0-2) = 'a' ;
    *(p0-1) = 'i' ;
    while (*p1 != '\n')
	*p0++ = *p1++ ;
    *p0++ = *p1++ ;


    /** update header_bytes **/

    sprintf(str, "%0*d", 7, p0-saiheader);
    p0 = HeaderString( saiheader , "header_bytes" ) ;
    s = str;
    while(*p0 != '\n')
	*p0++ = *s++ ;


    return saiheader;
}