/* 
 * Copyright (C) 1997-2005 Kare Sjolander <kare@speech.kth.se>
 *
 * This file is part of the Snack Sound Toolkit.
 * The latest version can be found at http://www.speech.kth.se/snack/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "tcl.h"
#include "snack.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define USE_OLD_CANVAS /* To keep Tk8.3 happy */
#include "tk.h"
#include "jkCanvItems.h"
#include <string.h>

/*
 * Wave item structure
 */

typedef struct WaveItem  {

  Tk_Item header;
  Tk_Canvas canvas;
  double x, y;
  Tk_Anchor anchor;
  double *x0;
  double *y0;
  double *x1;
  double *y1;
  XColor *fg;
  Pixmap fillStipple;
  GC gc;
  char *newSoundName;
  char *soundName;
  Sound *sound;
  int channel;
  int channelSet;
  int nchannels;
  int samprate;
  int encoding;
  float **blocks;
  int bufPos;
  double limit;
  int subSample;
  double pixpsec;
  int height;
  int width;
  int widthSet;
  int startSmp;
  int endSmp;
  int ssmp;
  int esmp;
  int zeroLevel;
  int frame;
  int id;
  int mode;
  int subSampleInt;
  char *channelStr;
  int debug;
  int storeType;
  char *preCompFile;
  struct WaveItem *preWI;
  Sound *preSound;
  int preCompInvalid;
  int validStart;
  char *progressCmd;
  Tcl_Obj *cmdPtr;
  Tcl_Interp *interp;
  int trimstart;
  float maxv;
  float minv;
  int remove; /* remove for 2.1 */

} WaveItem;

Tk_CustomOption waveTagsOption = { (Tk_OptionParseProc *) NULL,
				   (Tk_OptionPrintProc *) NULL,
				   (ClientData) NULL };

typedef enum {
  OPTION_ANCHOR,
  OPTION_TAGS,
  OPTION_SOUND,
  OPTION_HEIGHT,
  OPTION_WIDTH,
  OPTION_PIXPSEC,
  OPTION_START,
  OPTION_END,
  OPTION_FILL,
  OPTION_STIPPLE,
  OPTION_ZEROLEVEL,
  OPTION_FRAME,
  OPTION_LIMIT,
  OPTION_SUBSAMPLE,
  OPTION_CHANNEL,
  OPTION_PRECOMPWAVE,
  OPTION_PROGRESS,
  OPTION_TRIMSTART
} ConfigSpec;

static Tk_ConfigSpec configSpecs[] = {

  {TK_CONFIG_ANCHOR, "-anchor", (char *) NULL, (char *) NULL,
   "nw", Tk_Offset(WaveItem, anchor), TK_CONFIG_DONT_SET_DEFAULT},

  {TK_CONFIG_CUSTOM, "-tags", (char *) NULL, (char *) NULL,
   (char *) NULL, 0, TK_CONFIG_NULL_OK, &waveTagsOption},
  
  {TK_CONFIG_STRING, "-sound", (char *) NULL, (char *) NULL,
   "", Tk_Offset(WaveItem, newSoundName), TK_CONFIG_NULL_OK},
  
  {TK_CONFIG_INT, "-height", (char *) NULL, (char *) NULL,
   "100", Tk_Offset(WaveItem, height), 0},
  
  {TK_CONFIG_PIXELS, "-width", (char *) NULL, (char *) NULL,
   "378", Tk_Offset(WaveItem, widthSet), 0},
  
  {TK_CONFIG_DOUBLE, "-pixelspersecond", "pps", (char *) NULL,
   "250.0", Tk_Offset(WaveItem, pixpsec), 0},

  {TK_CONFIG_INT, "-start", (char *) NULL, (char *) NULL,
   "0", Tk_Offset(WaveItem, startSmp), 0},
  
  {TK_CONFIG_INT, "-end", (char *) NULL, (char *) NULL,
   "-1", Tk_Offset(WaveItem, endSmp), 0},
  
  {TK_CONFIG_COLOR, "-fill", (char *) NULL, (char *) NULL,
   "black", Tk_Offset(WaveItem, fg), TK_CONFIG_NULL_OK},
  
  {TK_CONFIG_BITMAP, "-stipple", (char *) NULL, (char *) NULL,
   (char *) NULL, Tk_Offset(WaveItem, fillStipple), TK_CONFIG_NULL_OK},

  {TK_CONFIG_BOOLEAN, "-zerolevel", "zerolevel", (char *) NULL,
   "yes", Tk_Offset(WaveItem, zeroLevel), TK_CONFIG_NULL_OK},

  {TK_CONFIG_BOOLEAN, "-frame", (char *) NULL, (char *) NULL,
   "no", Tk_Offset(WaveItem, frame), TK_CONFIG_NULL_OK},

  {TK_CONFIG_DOUBLE, "-limit", (char *) NULL, (char *) NULL,
   "-1.0", Tk_Offset(WaveItem, limit), 0},
  
  {TK_CONFIG_INT, "-subsample", (char *) NULL, (char *) NULL,
   "1", Tk_Offset(WaveItem, subSampleInt), TK_CONFIG_NULL_OK},

  {TK_CONFIG_STRING, "-channel", (char *) NULL, (char *) NULL,
   "-1", Tk_Offset(WaveItem, channelStr), TK_CONFIG_NULL_OK},

  {TK_CONFIG_STRING, "-shapefile", (char *) NULL, (char *) NULL,
   "", Tk_Offset(WaveItem, preCompFile), TK_CONFIG_NULL_OK},

  {TK_CONFIG_STRING, "-progress", (char *) NULL, (char *) NULL,
   "", Tk_Offset(WaveItem, progressCmd), TK_CONFIG_NULL_OK},

  {TK_CONFIG_INT, "-trimstart", (char *) NULL, (char *) NULL,
   "0", Tk_Offset(WaveItem, trimstart), 0},

  /* To be removed for 2.1 */
  {TK_CONFIG_INT, "-tround", (char *) NULL, (char *) NULL,
   "0", Tk_Offset(WaveItem, remove), 0},

  {TK_CONFIG_INT, "-debug", (char *) NULL, (char *) NULL,
   "0", Tk_Offset(WaveItem, debug), 0},

  {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
   (char *) NULL, 0, 0}

};

/*
 * Protos
 */

  static void   ComputeWaveBbox(Tk_Canvas canvas, WaveItem *wavePtr);

  static int    ComputeWaveCoords(Tk_Item *itemPtr);
  
  static int    ConfigureWave(Tcl_Interp *interp, Tk_Canvas canvas, 
			      Tk_Item *itemPtr, int argc,
			      char **argv, int flags);

  static int    CreateWave(Tcl_Interp *interp, Tk_Canvas canvas,
			   struct Tk_Item *itemPtr,
			   int argc, char **argv);

  static void   DeleteWave(Tk_Canvas canvas, Tk_Item *itemPtr,
			   Display *display);

  static void   DisplayWave(Tk_Canvas canvas, Tk_Item *itemPtr,
			    Display *display, Drawable dst,
			    int x, int y, int width, int height);

  static void   ScaleWave(Tk_Canvas canvas, Tk_Item *itemPtr,
			  double originX, double originY,
			  double scaleX, double scaleY);

  static void   TranslateWave(Tk_Canvas canvas, Tk_Item *itemPtr,
			      double deltaX, double deltaY);
  
  static int    WaveCoords(Tcl_Interp *interp, Tk_Canvas canvas,
			   Tk_Item *itemPtr, int argc, char **argv);
  
  static int    WaveToArea(Tk_Canvas canvas, Tk_Item *itemPtr,
			   double *rectPtr);
  
  static double WaveToPoint(Tk_Canvas canvas, Tk_Item *itemPtr,
			    double *coords);
  
  static int    WaveToPS(Tcl_Interp *interp, Tk_Canvas canvas,
			 Tk_Item *itemPtr, int prepass);

/*
 * Wave item type
 */

Tk_ItemType snackWaveType = {
  "waveform",
  sizeof(WaveItem),
  CreateWave,
  configSpecs,
  ConfigureWave,
  WaveCoords,
  DeleteWave,
  DisplayWave,
  0,
  WaveToPoint,
  WaveToArea,
  WaveToPS,
  ScaleWave,
  TranslateWave,
  (Tk_ItemIndexProc *) NULL,
  (Tk_ItemCursorProc *) NULL,
  (Tk_ItemSelectionProc *) NULL,
  (Tk_ItemInsertProc *) NULL,
  (Tk_ItemDCharsProc *) NULL,
  (Tk_ItemType *) NULL
};

static int
CreateWave(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr,
	   int argc, char **argv)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;
  int i;

  if (argc < 1) {
    Tcl_AppendResult(interp, "wrong # args: should be \"",
		     Tk_PathName(Tk_CanvasTkwin(canvas)), " create ",
		     itemPtr->typePtr->name, " coords ?opts?\"", (char *) NULL);
    return TCL_ERROR;
  }

  wavePtr->canvas = canvas;
  wavePtr->anchor = TK_ANCHOR_NW;
  wavePtr->x0 = NULL;
  wavePtr->y0 = NULL;
  wavePtr->x1 = NULL;
  wavePtr->y1 = NULL;
  wavePtr->fg = None;
  wavePtr->fillStipple = None;
  wavePtr->gc = None;
  wavePtr->newSoundName = NULL;
  wavePtr->soundName = NULL;
  wavePtr->sound = NULL;
  wavePtr->pixpsec = 250.0;
  wavePtr->height = 100;
  wavePtr->width = -1;
  wavePtr->widthSet = 378;
  wavePtr->startSmp = 0;
  wavePtr->endSmp = -1;
  wavePtr->ssmp = 0;
  wavePtr->esmp = -1;
  wavePtr->id = 0;
  wavePtr->mode = CONF_WIDTH;
  wavePtr->zeroLevel = 1;
  wavePtr->frame = 0;
  wavePtr->channelStr = NULL;
  wavePtr->channel = -1;
  wavePtr->channelSet = -1;
  wavePtr->nchannels = 1;
  wavePtr->samprate = 16000;
  wavePtr->encoding = LIN16;
  wavePtr->bufPos = 0;
  wavePtr->limit = -1.0;
  wavePtr->subSampleInt = 1;
  wavePtr->subSample = 1;
  wavePtr->preCompFile = NULL;
  wavePtr->preSound = NULL;
  wavePtr->preWI = NULL;
  wavePtr->preCompInvalid = 0;
  wavePtr->validStart = 0;
  wavePtr->progressCmd = NULL;
  wavePtr->cmdPtr = NULL;
  wavePtr->interp = interp;
  wavePtr->trimstart = 0;
  wavePtr->maxv = 0.0f;
  wavePtr->minv = 0.0f;
  wavePtr->debug = 0;
  wavePtr->x = 0;
  wavePtr->y = 0;

  for (i = 1; i < argc; i++) {
      if ((argv[i][0] == '-') && (argv[i][1] >= 'a') && (argv[i][1] <= 'z')) {
	  break;
      }
  }

 if (WaveCoords(interp, canvas, (Tk_Item*)wavePtr, i, argv) != TCL_OK)
    return TCL_ERROR;
  
  if (ConfigureWave(interp, canvas, itemPtr, argc-i, argv+i, 0) == TCL_OK)
    return TCL_OK;

  DeleteWave(canvas, itemPtr, Tk_Display(Tk_CanvasTkwin(canvas)));
  return TCL_ERROR;
}

static void
WaveMaxMin(WaveItem *wavePtr, SnackLinkedFileInfo *info, int start, int stop,
	   float *maxi, float *mini)
{
  int i, j, allFlag = 0;
  float maxval = -8388608.0, minval = 8388607.0, val;
  int nchan = wavePtr->nchannels, chan = wavePtr->channel;
  int inc = nchan * wavePtr->subSample;

  if (start < 0 || stop > wavePtr->bufPos - 1 || stop == 0 ||
      (wavePtr->blocks[0] == NULL && wavePtr->storeType == SOUND_IN_MEMORY)) {
    if (wavePtr->encoding == LIN8OFFSET) {
      *maxi = 128.0;
      *mini = 128.0;
    } else {
      *maxi = 0.0;
      *mini = 0.0;
    }
    return;
  }
  if (chan == -1) {
    allFlag = 1;
    chan = 0;
  }

  start = start * wavePtr->nchannels + chan;
  stop  = stop  * wavePtr->nchannels + chan + wavePtr->nchannels - 1;

  for (i = start; i <= stop; i += inc) {
    if (wavePtr->storeType == SOUND_IN_MEMORY) {
      val = FSAMPLE(wavePtr, i);
      if (allFlag) {
	for (j = 1; j < nchan; j++) {
	  val += FSAMPLE(wavePtr, i + j);
	}
	val = val / nchan;
      }
    } else {
      val = GetSample(info, i);	
      if (allFlag) {
	for (j = 1; j < nchan; j++) {
	  val += GetSample(info, i + j);
	}
	val = val / nchan;
      }
    }
    if (val > maxval) {
      maxval = val;
    }
    if (val < minval) {
      minval = val;
    }
  }
  if (wavePtr->limit > 0.0) {
    if (maxval > wavePtr->limit) {
      maxval = (float) wavePtr->limit;
    }
    if (minval < -wavePtr->limit) {
      minval = (float) -wavePtr->limit;
    }
  }
  *maxi = maxval;
  *mini = minval;
}

static int
WaveCoords(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int argc,
	   char **argv)
{
  WaveItem *wPtr = (WaveItem *) itemPtr;
  char xc[TCL_DOUBLE_SPACE], yc[TCL_DOUBLE_SPACE];
  char **oargv = argv;
  int result = TCL_OK;

  if (argc == 0) {
    Tcl_PrintDouble(interp, wPtr->x, xc);
    Tcl_PrintDouble(interp, wPtr->y, yc);
    Tcl_AppendResult(interp, xc, " ", yc, (char *) NULL);
    return TCL_OK;
  }
  
  if (argc == 1) {
      if (Tcl_SplitList(interp, argv[0], &argc, &argv) != TCL_OK) {
	  return TCL_ERROR;
      }
  }

  if (argc == 2) {
    if ((Tk_CanvasGetCoord(interp, canvas, argv[0], &wPtr->x) != TCL_OK) ||
	(Tk_CanvasGetCoord(interp, canvas, argv[1], &wPtr->y) != TCL_OK)) {
      result = TCL_ERROR;
    } else {
      ComputeWaveBbox(canvas, wPtr);
    }
  } else {
    char buf[80];

    sprintf(buf, "wrong # coordinates: expected 0 or 2, got %d", argc);
    Tcl_SetResult(interp, buf, TCL_VOLATILE);

    result = TCL_ERROR;
  }
done:
  if (oargv != argv) {
      ckfree( (char*)argv);
  }
  return result;
}

/*#define WIDEWAVE 100000*/

static int
ComputeWaveCoords(Tk_Item *itemPtr)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;
  int i = 0;
  float maxv, minv, abmax;
  int yh = wavePtr->height / 2;
  int nPoints = wavePtr->width;
  SnackLinkedFileInfo info;
  Tcl_Interp *interp;
  int usePre = 0;

  if (wavePtr->debug > 1) Snack_WriteLog("  Enter ComputeWaveCoords\n"); 

  if (wavePtr->x0 != NULL) {
    ckfree((char *) wavePtr->x0);
  }
  if (wavePtr->y0 != NULL) {
    ckfree((char *) wavePtr->y0);
  }
  if (wavePtr->x1 != NULL) {
    ckfree((char *) wavePtr->x1);
  }
  if (wavePtr->y1 != NULL) {
    ckfree((char *) wavePtr->y1);
  }
  wavePtr->x0 = (double *) ckalloc(sizeof(double) * nPoints);
  wavePtr->y0 = (double *) ckalloc(sizeof(double) * nPoints);
  wavePtr->x1 = (double *) ckalloc(sizeof(double) * nPoints);
  wavePtr->y1 = (double *) ckalloc(sizeof(double) * nPoints);

  if (wavePtr->sound == NULL) {
    for (i = 0; i < nPoints; i++) {
      wavePtr->x0[i] = (double) i;
      wavePtr->y0[i] = (double) yh;
      wavePtr->x1[i] = (double) i;
      wavePtr->y1[i] = (double) yh;
    }
    return TCL_OK;
  }

  maxv = wavePtr->sound->maxsamp;
  minv = wavePtr->sound->minsamp;
  abmax = wavePtr->sound->abmax;
  interp = wavePtr->sound->interp;

  if (wavePtr->preCompFile != NULL && wavePtr->sound->readStatus != READ) {
    char *type = NULL;

    if (wavePtr->preSound != NULL) {
      wavePtr->preSound->fcname = NULL;
      Snack_DeleteSound(wavePtr->preSound);
    }
    wavePtr->preSound = Snack_NewSound(200, LIN8, wavePtr->sound->nchannels);
    if (wavePtr->preSound != NULL) {
      wavePtr->preSound->fcname = wavePtr->preCompFile;
      type = LoadSound(wavePtr->preSound, interp, NULL, 0, -1);
      if (wavePtr->preWI != NULL) ckfree((char *)wavePtr->preWI);
      wavePtr->preWI = (WaveItem *) ckalloc(sizeof(WaveItem));
      if (wavePtr->preWI != NULL) {
	wavePtr->preWI->nchannels = wavePtr->preSound->nchannels;
	wavePtr->preWI->channel = 0;
	wavePtr->preWI->subSample = 1;
	wavePtr->preWI->bufPos = wavePtr->preSound->length;
	wavePtr->preWI->blocks = wavePtr->preSound->blocks;
	wavePtr->preWI->storeType = SOUND_IN_MEMORY;
	wavePtr->preWI->encoding = LIN8;
	wavePtr->preWI->limit = wavePtr->limit;
      }
    }

    if ((type == NULL || wavePtr->preCompInvalid) && wavePtr->preSound!=NULL) {

      /* Compute and store wave */

      int nStore = (int) (200.0 * wavePtr->sound->length 
			  / wavePtr->sound->samprate);
      int j;
      int tmp = wavePtr->channel;
      
      maxv = 0.0f;
      minv = 0.0f;
      if (wavePtr->debug > 2) Snack_WriteLog("    Saving computed waveform\n");
      wavePtr->preCompInvalid = 0;
      Snack_ResizeSoundStorage(wavePtr->preSound, nStore);
      wavePtr->preSound->length = nStore;
      if (wavePtr->cmdPtr != NULL) {
	Snack_ProgressCallback(wavePtr->cmdPtr, interp,
			       "Computing waveform", 0.0);
      }

      if (wavePtr->storeType != SOUND_IN_MEMORY) {
	if (OpenLinkedFile(wavePtr->sound, &info) != TCL_OK) {
	  for (i = 0; i < nPoints; i++) {
	    wavePtr->x0[i] = (double) i;
	    wavePtr->y0[i] = (double) yh;
	    wavePtr->x1[i] = (double) i;
	    wavePtr->y1[i] = (double) yh;
	  }
	  if (wavePtr->cmdPtr != NULL) {
	    Snack_ProgressCallback(wavePtr->cmdPtr, interp,
				   "Computing waveform", 1.0);
	  }
	  return TCL_OK;
	}
      }
      for (i = 0; i < nStore / 2; i++) {
	for (j = 0; j < wavePtr->sound->nchannels; j++) {
	  float fraq = (float) wavePtr->sound->length / (nStore / 2);
	  int start = (int) (i     * fraq);
	  int stop  = (int) ((i+1) * fraq);
	  float wtop, wbot;

	  wavePtr->channel = j;
	  WaveMaxMin(wavePtr, &info, start, stop, &wtop, &wbot);
	  
	  if (maxv < wtop) maxv = wtop;
	  if (minv > wbot) minv = wbot;
	  
	  switch (wavePtr->encoding) {
	  case LIN16:
	  case MULAW:
	  case ALAW:
	    wtop = wtop / 256.0f;
	    wbot = wbot / 256.0f;
	    break;
	  case LIN24:
	    wtop = wtop / 65536.0f;
	    wbot = wbot / 65536.0f;
	    break;
	  case LIN32:
	    wtop = wtop / 16777216.0f;
	    wbot = wbot / 16777216.0f;
	    break;
	  case SNACK_FLOAT:
	    wtop = (wtop / abmax) * 128.0f;
	    wbot = (wbot / abmax) * 128.0f;
	    break;
	  case LIN8OFFSET:
	    wtop -= 128.0f;
	    wbot -= 128.0f;
	    break;
	  case LIN8:
	    break;
	  }
	  Snack_SetSample(wavePtr->preSound, j, i*2,   (char) wtop);
	  Snack_SetSample(wavePtr->preSound, j, i*2+1, (char) wbot);
	  if (j == 0 && (wavePtr->cmdPtr != NULL) && ((i % 1000) == 999)) {
	    int res = Snack_ProgressCallback(wavePtr->cmdPtr, interp,
			     "Computing waveform", (double) i/(nStore/2));
	    if (res != TCL_OK) {
	      if (wavePtr->debug > 2) {
		Snack_WriteLog("    Aborting ComputeWaveCoords\n"); 
	      }
	      for (;i < nStore / 2; i++) {
		for (j = 0; j < wavePtr->sound->nchannels; j++) {
		  Snack_SetSample(wavePtr->preSound, j, i*2,   (char) 0);
		  Snack_SetSample(wavePtr->preSound, j, i*2+1, (char) 0);
		}
	      }
	      break;
	    }
	  }
	}
      }
      if (wavePtr->cmdPtr != NULL) {
	Snack_ProgressCallback(wavePtr->cmdPtr, interp,
			       "Computing waveform", 1.0);
      }
      if (SaveSound(wavePtr->preSound, interp, wavePtr->preCompFile, NULL,
		    0, NULL, 0, wavePtr->preSound->length, AIFF_STRING) ==
	  TCL_ERROR) {
	if (wavePtr->debug > 2) Snack_WriteLog("    Failed saving waveform\n");
	wavePtr->preCompFile = NULL;
      }
      wavePtr->preWI->bufPos = wavePtr->preSound->length;
      wavePtr->preWI->blocks = wavePtr->preSound->blocks;
      if (wavePtr->storeType != SOUND_IN_MEMORY) {
	CloseLinkedFile(&info);
      }
      wavePtr->channel = tmp;
    }

    if (wavePtr->preSound != NULL && wavePtr->preWI != NULL) {

      /* Use precomputed wave */

      float left  = ((float) wavePtr->ssmp / wavePtr->sound->length) *
	wavePtr->preSound->length;
      float right = ((float) wavePtr->esmp / wavePtr->sound->length) *
	wavePtr->preSound->length;
      float fraq  = (right - left) / (nPoints * 2);

      if (fraq > 1.0) {
	usePre = 1;
	switch (wavePtr->encoding) {
	case LIN16:
	case MULAW:
	case ALAW:
	  maxv = maxv / 256.0f;
	  minv = minv / 256.0f;
	  break;
	case LIN24:
	  maxv = maxv / 65536.0f;
	  minv = minv / 65536.0f;
	  break;
	case LIN32:
	  maxv = maxv / 16777216.0f;
	  minv = minv / 16777216.0f;
	  break;
	case SNACK_FLOAT:
	  maxv = (maxv / abmax) * 128.0f;
	  minv = (minv / abmax) * 128.0f;
	  break;
	case LIN8OFFSET:
	  maxv -= 128.0f;
	  minv -= 128.0f;
	  break;
	case LIN8:
	  break;
	}

	if (wavePtr->debug > 2) {
	  Snack_WriteLog("    Using precomputed waveform\n");
	}

	wavePtr->preWI->channel = wavePtr->channel;
	for (i = 0; i < nPoints; i++) {
	  int start = (int) (left + 2*(i * fraq));
	  int stop  = (int) (left + 2*(i+1)*fraq);
	  float wtop, wbot;

	  WaveMaxMin(wavePtr->preWI, NULL, start, stop, &wtop, &wbot);

	  if (maxv < wtop) maxv = wtop;
	  if (minv > wbot) minv = wbot;

	  wavePtr->x0[i] = i;
	  wavePtr->x1[i] = i;
	  if (i > 0 && wavePtr->y1[i-1] <= wtop) {
	    wavePtr->y0[i] = wtop;
	    wavePtr->y1[i] = wbot;
	  } else {
	    wavePtr->y0[i] = wbot;
	    wavePtr->y1[i] = wtop;
	  }
	}

	if (wavePtr->encoding == LIN8OFFSET) {
	  maxv += 128.0f;
	  minv += 128.0f;
	}
      } else {
	usePre = 0;
      }
    }
  }

  if (!usePre) {

    if (wavePtr->debug > 2) {
      Snack_WriteLog("    Default waveform computation\n");
    }

    if (wavePtr->storeType != SOUND_IN_MEMORY) {
      if (OpenLinkedFile(wavePtr->sound, &info) != TCL_OK) {
	for (i = 0; i < nPoints; i++) {
	  wavePtr->x0[i] = (double) i;
	  wavePtr->y0[i] = (double) yh;
	  wavePtr->x1[i] = (double) i;
	  wavePtr->y1[i] = (double) yh;
	}
	  return TCL_OK;
      }
    }

    for (i = 0; i < nPoints; i++) {
      float fraq = (float) (wavePtr->esmp - wavePtr->ssmp) / nPoints;
      int start = wavePtr->ssmp + (int) (i     * fraq) - wavePtr->validStart;
      int stop  = wavePtr->ssmp + (int) ((i+1) * fraq) - wavePtr->validStart;
      float wtop, wbot;

      if (wavePtr->trimstart == 1) {
	start = (int)(wavePtr->subSample*ceil((float)start/wavePtr->subSample));
      }

      WaveMaxMin(wavePtr, &info, start, stop, &wtop, &wbot);

      if (maxv < wtop) maxv = wtop;
      if (minv > wbot) minv = wbot;

      if (wavePtr->encoding == LIN8OFFSET) {
	wtop -= 128.0f;
	wbot -= 128.0f;
      }

      wavePtr->x0[i] = i;
      wavePtr->x1[i] = i;
      if (i > 0 && wavePtr->y1[i-1] <= wtop) {
	wavePtr->y0[i] = wtop;
	wavePtr->y1[i] = wbot;
      } else {
	wavePtr->y0[i] = wbot;
	wavePtr->y1[i] = wtop;
      }
    }
    if (wavePtr->storeType != SOUND_IN_MEMORY) {
      CloseLinkedFile(&info);
    }
  }

  if (maxv > wavePtr->sound->maxsamp) {
    wavePtr->sound->maxsamp = maxv;
  }
  if (minv < wavePtr->sound->minsamp) {
    wavePtr->sound->minsamp = minv;
  }

  if (wavePtr->limit > 0) {
    maxv = (float) wavePtr->limit;
    minv = (float) -wavePtr->limit;
  }
  if (wavePtr->encoding == LIN8OFFSET) {
    maxv -= 128.0f;
    minv -= 128.0f;
  }

  wavePtr->maxv = maxv;
  wavePtr->minv = minv;

  ComputeWaveBbox(wavePtr->canvas, wavePtr);

  if (usePre) {
    switch (wavePtr->encoding) {
    case LIN16:
    case MULAW:
    case ALAW:
      maxv = maxv * 256.0f;
      minv = minv * 256.0f;
      break;
    case LIN24:
      maxv = maxv * 65536.0f;
      minv = minv * 65536.0f;
      break;
    case LIN32:
      maxv = maxv / 16777216.0f;
      minv = minv / 16777216.0f;
      break;
    case SNACK_FLOAT:
      maxv = maxv / 128.0f;
      minv = minv / 128.0f;
      break;
    case LIN8OFFSET:
      maxv += 128.0f;
      minv += 128.0f;
      break;
    case LIN8:
      break;
    }
    if (maxv > wavePtr->sound->maxsamp) {
      wavePtr->sound->maxsamp = maxv;
    }
    if (minv < wavePtr->sound->minsamp) {
      wavePtr->sound->minsamp = minv;
    }
  }

  if (wavePtr->debug > 1) Snack_WriteLog("  Exit ComputeWaveCoords\n"); 

  return TCL_OK;
}

#define NSAMPLES 100000

static void
UpdateWave(ClientData clientData, int flag)
{
  WaveItem *wavePtr = (WaveItem *) clientData;
  Sound *s = wavePtr->sound;

  if (wavePtr->debug > 1) Snack_WriteLogInt("  Enter UpdateWave", flag);

  if (wavePtr->canvas == NULL || wavePtr->sound == NULL) return;

  if (flag == SNACK_DESTROY_SOUND) {
    wavePtr->sound = NULL;
    if (wavePtr->id) Snack_RemoveCallback(s, wavePtr->id);
    wavePtr->id = 0;
    return;
  }

  Tk_CanvasEventuallyRedraw(wavePtr->canvas,
			    wavePtr->header.x1, wavePtr->header.y1, 
			    wavePtr->header.x2, wavePtr->header.y2);

  wavePtr->blocks = s->blocks;
  wavePtr->bufPos = s->length;
  wavePtr->storeType = s->storeType;

  if ((flag == SNACK_MORE_SOUND) || (wavePtr->endSmp < 0)) {
    wavePtr->esmp = wavePtr->bufPos - 1;
  }
  
  if (wavePtr->esmp > wavePtr->bufPos - 1)
    wavePtr->esmp = wavePtr->bufPos - 1;
  
  if (wavePtr->endSmp > 0) 
    wavePtr->esmp = wavePtr->endSmp;
  
  if (wavePtr->endSmp > wavePtr->bufPos - 1)
    wavePtr->esmp = wavePtr->bufPos - 1;
  
  wavePtr->ssmp = wavePtr->startSmp;
  
  if (wavePtr->ssmp > wavePtr->esmp)
    wavePtr->ssmp = wavePtr->esmp;

  wavePtr->samprate = s->samprate;
  wavePtr->encoding = s->encoding;
  wavePtr->nchannels = s->nchannels;

  wavePtr->channel = wavePtr->channelSet;
  if (wavePtr->nchannels == 1) {
    wavePtr->channel = 0;
  }

  if (wavePtr->mode == CONF_WIDTH) {
    if (wavePtr->esmp != wavePtr->ssmp) {
      wavePtr->pixpsec = (double) wavePtr->width * wavePtr->samprate /
	(wavePtr->esmp - wavePtr->ssmp);
    }
  }
  else if (wavePtr->mode == CONF_PPS) {

    wavePtr->width = (int)((wavePtr->esmp - wavePtr->ssmp) * 
			   wavePtr->pixpsec / wavePtr->samprate/* + 0.5*/);
  } 
  else if (wavePtr->mode == CONF_WIDTH_PPS) {
    wavePtr->ssmp = (int) (wavePtr->esmp - wavePtr->width *
			   wavePtr->samprate / wavePtr->pixpsec);
  }

  if (wavePtr->subSampleInt == 0) {
    if (wavePtr->esmp - wavePtr->ssmp > NSAMPLES) { 
      wavePtr->subSample = (wavePtr->esmp - wavePtr->ssmp) / NSAMPLES;
    } else {
      wavePtr->subSample = 1;
    }
  } else {
    wavePtr->subSample = wavePtr->subSampleInt;
  }

  wavePtr->preCompInvalid = 1;
  wavePtr->validStart = s->validStart;

  if (ComputeWaveCoords((Tk_Item *)wavePtr) != TCL_OK) {
    return;
  }
  Tk_CanvasEventuallyRedraw(wavePtr->canvas,
			    wavePtr->header.x1, wavePtr->header.y1,
			    wavePtr->header.x2, wavePtr->header.y2);

  if (wavePtr->debug > 1) {
    Snack_WriteLogInt("  Exit UpdateWave", wavePtr->width);
  }
}

static int
ConfigureWave(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, 
	      int argc, char **argv, int flags)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;
  Sound *s = wavePtr->sound;
  Tk_Window tkwin = Tk_CanvasTkwin(canvas);
  XGCValues gcValues;
  GC newGC;
  unsigned long mask;
  int doCompute = 0, oldMode;
  int i, j;

  if (argc == 0) return TCL_OK;

  if (Tk_ConfigureWidget(interp, tkwin, configSpecs, argc,
			 (CONST84 char **)argv,
			 (char *) wavePtr, flags) != TCL_OK) return TCL_ERROR;

  if (wavePtr->debug > 1) Snack_WriteLog("  Enter ConfigureWave\n");

  for (i = 0; configSpecs[i].type != TK_CONFIG_END; i++) {
    for (j = 0; j < argc; j += 2) {
      if (strncmp(argv[j], configSpecs[i].argvName, strlen(argv[j])) == 0) {
	configSpecs[i].specFlags |= TK_CONFIG_OPTION_SPECIFIED;
	break;
      }
    }
  }

#if defined(MAC)
  for (i = 0; i < argc; i++) {
    if (strncmp(argv[i], "-anchor", strlen(argv[i])) == 0) {
      i++;
      if (strcmp(argv[i], "ne") == 0) {
	wavePtr->anchor = 1;
      } else if (strcmp(argv[i], "nw") == 0) {
	wavePtr->anchor = 7;
      } else if (strcmp(argv[i], "n") == 0) {
	wavePtr->anchor = 0;
      } else if (strcmp(argv[i], "e") == 0) {
	wavePtr->anchor = 2;
      } else if (strcmp(argv[i], "se") == 0) {
	wavePtr->anchor = 3;
      } else if (strcmp(argv[i], "sw") == 0) {
	wavePtr->anchor = 5;
      } else if (strcmp(argv[i], "s") == 0) {
	wavePtr->anchor = 4;
      } else if (strcmp(argv[i], "w") == 0) {
	wavePtr->anchor = 6;
      } else if (strncmp(argv[i], "center", strlen(argv[i])) == 0) {
	wavePtr->anchor = 8;
      }
      break;
    }
  }
#endif

  if (OptSpecified(OPTION_SOUND)) {
    if (wavePtr->newSoundName == NULL) {
      wavePtr->sound = NULL;
      if (wavePtr->id) Snack_RemoveCallback(s, wavePtr->id);
      wavePtr->id = 0;
    } else {
      if ((s = Snack_GetSound(interp, wavePtr->newSoundName)) == NULL) {
	return TCL_ERROR;
      }
      if (s->storeType == SOUND_IN_CHANNEL) {
	Tcl_AppendResult(interp, wavePtr->newSoundName, 
			 " can not be linked to a channel", (char *) NULL);
	return TCL_ERROR;
      }
      if (s->storeType == SOUND_IN_FILE) {
	s->itemRefCnt++;
      }
      wavePtr->sound = s;
      if (wavePtr->soundName == NULL) {
	wavePtr->soundName = ckalloc(strlen(wavePtr->newSoundName)+1);
	strcpy(wavePtr->soundName, wavePtr->newSoundName);
      }
      if (strcmp(wavePtr->soundName, wavePtr->newSoundName) != 0) {
	Sound *t = Snack_GetSound(interp, wavePtr->soundName);
	ckfree(wavePtr->soundName);
	wavePtr->soundName = ckalloc(strlen(wavePtr->newSoundName)+1);
	strcpy(wavePtr->soundName, wavePtr->newSoundName);
	wavePtr->width = 0;
	wavePtr->ssmp    = 0;
	wavePtr->esmp    = -1;
	Snack_RemoveCallback(t, wavePtr->id);
	wavePtr->id = 0;
      }
      
      if (!wavePtr->id)
	wavePtr->id = Snack_AddCallback(s, UpdateWave, (int *)wavePtr);
      
      wavePtr->blocks = s->blocks;
      wavePtr->bufPos = s->length;
      wavePtr->samprate = s->samprate;
      wavePtr->encoding = s->encoding;
      wavePtr->nchannels = s->nchannels;
      wavePtr->storeType = s->storeType;
    }
    doCompute = 1;
  }
  wavePtr->esmp = wavePtr->endSmp;

  if (wavePtr->endSmp < 0)
    wavePtr->esmp = wavePtr->bufPos - 1;

  if (wavePtr->endSmp > wavePtr->bufPos - 1)
    wavePtr->esmp = wavePtr->bufPos - 1;

  if (wavePtr->startSmp > wavePtr->endSmp && wavePtr->endSmp >= 0)
    wavePtr->startSmp = wavePtr->endSmp;

  if (wavePtr->startSmp < 0)
    wavePtr->startSmp = 0;

  wavePtr->ssmp = wavePtr->startSmp;

  if (wavePtr->ssmp > wavePtr->esmp)
    wavePtr->ssmp = wavePtr->esmp;

  if (OptSpecified(OPTION_START)) {
    doCompute = 1;
  }

  if (OptSpecified(OPTION_END)) {
    doCompute = 1;
  }

  if (OptSpecified(OPTION_LIMIT)) {
    doCompute = 1;
  }

  if (OptSpecified(OPTION_SUBSAMPLE)) {
    doCompute = 1;
  }

  oldMode = wavePtr->mode;
  if (OptSpecified(OPTION_PIXPSEC) && OptSpecified(OPTION_WIDTH)) {
    wavePtr->mode = CONF_WIDTH_PPS;
    doCompute = 1;
  }
  else if (OptSpecified(OPTION_PIXPSEC)) {
    wavePtr->mode = CONF_PPS;
    doCompute = 1;
  }
  else if (OptSpecified(OPTION_WIDTH)) {
    wavePtr->mode = CONF_WIDTH;
  }

  if (oldMode != wavePtr->mode) {
    doCompute = 1;
  }
  
  if (wavePtr->width != wavePtr->widthSet) {
    wavePtr->width = wavePtr->widthSet;
    doCompute = 1;
  }
  
  if (wavePtr->mode == CONF_WIDTH_PPS) {
    if (OptSpecified(OPTION_END) && !OptSpecified(OPTION_START)) {
      wavePtr->ssmp = (int) (wavePtr->esmp - wavePtr->width *
			     wavePtr->samprate / wavePtr->pixpsec);
    } else {
      wavePtr->esmp = (int) (wavePtr->ssmp + wavePtr->width *
			     wavePtr->samprate / wavePtr->pixpsec);
    }
  }
  else if (wavePtr->mode == CONF_PPS) {
    wavePtr->width = (int)((wavePtr->esmp - wavePtr->ssmp) * 
			   wavePtr->pixpsec / wavePtr->samprate/* + 0.5*/);
  }
  else if (wavePtr->mode == CONF_WIDTH) {
    if (wavePtr->esmp != wavePtr->ssmp) {
      wavePtr->pixpsec = (double) wavePtr->width * wavePtr->samprate /
	(wavePtr->esmp - wavePtr->ssmp);
    }
  }

  if (OptSpecified(OPTION_PRECOMPWAVE)) {
    wavePtr->preCompInvalid = 0;
    doCompute = 1;
  }

  if (OptSpecified(OPTION_CHANNEL)) {
    if (GetChannel(interp, wavePtr->channelStr, wavePtr->nchannels, 
		   &wavePtr->channelSet) != TCL_OK) {
      return TCL_ERROR;
    }
    doCompute = 1;
  }
  wavePtr->channel = wavePtr->channelSet;
  if (wavePtr->nchannels == 1) {
    wavePtr->channel = 0;
  }

  if (OptSpecified(OPTION_PROGRESS)) {
    if (wavePtr->progressCmd != NULL) {
      wavePtr->cmdPtr = Tcl_NewStringObj(wavePtr->progressCmd, -1);
      Tcl_IncrRefCount(wavePtr->cmdPtr);
    } else {
      if (wavePtr->cmdPtr != NULL) {
	Tcl_DecrRefCount(wavePtr->cmdPtr);
	wavePtr->cmdPtr = NULL;
      }
    }
  }

  if (wavePtr->subSampleInt == 0) {
    if (wavePtr->esmp - wavePtr->ssmp > NSAMPLES) { 
      wavePtr->subSample = (wavePtr->esmp - wavePtr->ssmp) / NSAMPLES;
    } else {
      wavePtr->subSample = 1;
    }
  } else {
    wavePtr->subSample = wavePtr->subSampleInt;
  }

  if (wavePtr->trimstart == 1 && wavePtr->width > 0) {
    int len = wavePtr->esmp - wavePtr->ssmp;
    double fraq = (double) len / wavePtr->width;
    
    if (fraq > 0.0) {
      wavePtr->ssmp = (int) (fraq * floor(wavePtr->ssmp/fraq));
      wavePtr->esmp = wavePtr->ssmp + len;
    }
    if (wavePtr->esmp > wavePtr->bufPos - 1)
      wavePtr->esmp = wavePtr->bufPos - 1;
  }


  if (wavePtr->fg == NULL) {
    newGC = None;
  } else {
    gcValues.foreground = wavePtr->fg->pixel;
    gcValues.line_width = 1;
    mask = GCForeground|GCLineWidth;
    if (wavePtr->fillStipple != None) {
      gcValues.stipple = wavePtr->fillStipple;
      gcValues.fill_style = FillStippled;
      mask |= GCStipple|GCFillStyle;
    }
    newGC = Tk_GetGC(tkwin, mask, &gcValues);
    gcValues.line_width = 0;
  }
  if (wavePtr->gc != None) {
    Tk_FreeGC(Tk_Display(tkwin), wavePtr->gc);
  }
  wavePtr->gc = newGC;
  
  ComputeWaveBbox(canvas, wavePtr);

  if (doCompute) {
    if (ComputeWaveCoords(itemPtr) != TCL_OK) {
      return TCL_ERROR;
    }
  }

  for (i = 0; configSpecs[i].type != TK_CONFIG_END; i++) {
    configSpecs[i].specFlags &= ~TK_CONFIG_OPTION_SPECIFIED;
  }
  
  if (wavePtr->debug > 1)
    Snack_WriteLogInt("  Exit ConfigureWave", wavePtr->width);

  return TCL_OK;
}

static void
DeleteWave(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;

  if ((wavePtr->id) &&
      (Snack_GetSound(wavePtr->interp, wavePtr->soundName) != NULL)) {
    Snack_RemoveCallback(wavePtr->sound, wavePtr->id);
  }

  if (wavePtr->soundName != NULL) ckfree(wavePtr->soundName);

  if (wavePtr->x0 != NULL) ckfree((char *) wavePtr->x0);
  if (wavePtr->y0 != NULL) ckfree((char *) wavePtr->y0);
  if (wavePtr->x1 != NULL) ckfree((char *) wavePtr->x1);
  if (wavePtr->y1 != NULL) ckfree((char *) wavePtr->y1);

  if (wavePtr->fg != NULL) Tk_FreeColor(wavePtr->fg);

  if (wavePtr->fillStipple != None) Tk_FreeBitmap(display, wavePtr->fillStipple);

  if (wavePtr->gc != None) Tk_FreeGC(display, wavePtr->gc);

  if (wavePtr->preWI != NULL) ckfree((char *)wavePtr->preWI);

  if (wavePtr->preSound != NULL) Snack_DeleteSound(wavePtr->preSound);

  if (wavePtr->sound != NULL) {
    if (wavePtr->sound->storeType == SOUND_IN_FILE) {
      wavePtr->sound->itemRefCnt--;
    }
  }

  if (wavePtr->cmdPtr != NULL) Tcl_DecrRefCount(wavePtr->cmdPtr);
}

static void
ComputeWaveBbox(Tk_Canvas canvas, WaveItem *wavePtr)
{
  int width = wavePtr->width;
  int height = wavePtr->height;
  int x = (int) (wavePtr->x + ((wavePtr->x >= 0) ? 0.5 : - 0.5));
  int y = (int) (wavePtr->y + ((wavePtr->y >= 0) ? 0.5 : - 0.5));
  
  switch (wavePtr->anchor) {
  case TK_ANCHOR_N:
    x -= width/2;
    break;
  case TK_ANCHOR_NE:
    x -= width;
    break;
  case TK_ANCHOR_E:
    x -= width;
    y -= height/2;
    break;
  case TK_ANCHOR_SE:
    x -= width;
    y -= height;
    break;
  case TK_ANCHOR_S:
    x -= width/2;
    y -= height;
    break;
  case TK_ANCHOR_SW:
    y -= height;
    break;
  case TK_ANCHOR_W:
    y -= height/2;
    break;
  case TK_ANCHOR_NW:
    break;
  case TK_ANCHOR_CENTER:
    x -= width/2;
    y -= height/2;
    break;
  }

  wavePtr->header.x1 = x;
  wavePtr->header.y1 = y;
  wavePtr->header.x2 = x + width;
  wavePtr->header.y2 = y + height;
}

static void
DisplayWave(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display,
	    Drawable drawable, int x, int y, int width, int height)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;
  int i;
  int xo = wavePtr->header.x1;
  int yo = wavePtr->header.y1;
  int ym = wavePtr->height / 2;
  int dx = max(x - xo, 0);
  float scale = 1000000.0f;
  XPoint fpts[5];

  if (wavePtr->debug > 1) Snack_WriteLogInt("  Enter DisplayWave", width);

  if (wavePtr->height == 0) return;

  if (wavePtr->gc == None) return;

  if (wavePtr->fillStipple != None)
    Tk_CanvasSetStippleOrigin(canvas, wavePtr->gc);

  if (wavePtr->height > 2) {
    scale = 2 * ((wavePtr->maxv > -wavePtr->minv)
		 ? wavePtr->maxv :
		 -wavePtr->minv) / (float)(wavePtr->height - 2);
  }
  if (scale < 0.00001f) {
    scale = 0.00001f;
  }

  if (dx + width > wavePtr->width) {
    width = wavePtr->width - dx;
  }
  if (dx > 0) {
    dx--;
    if (width < wavePtr->width - dx) width++;
    if (width < wavePtr->width - dx) width++;
  }
  for (i = dx; i < dx + width; i++) {
    Tk_CanvasDrawableCoords(canvas, xo + wavePtr->x0[i],
			    yo + ym - wavePtr->y0[i] / scale,
			    &fpts[0].x, &fpts[0].y);
    Tk_CanvasDrawableCoords(canvas, xo + wavePtr->x1[i],
			    yo + ym - wavePtr->y1[i] / scale,
			    &fpts[1].x, &fpts[1].y);
    Tk_CanvasDrawableCoords(canvas, xo + wavePtr->x1[i]+1,
			    yo + ym - wavePtr->y1[i] / scale,
			    &fpts[2].x, &fpts[2].y);
    XDrawLines(display, drawable, wavePtr->gc, fpts, 3, CoordModeOrigin);
  }
  
  if (wavePtr->zeroLevel) {
    Tk_CanvasDrawableCoords(canvas, (double) xo, 
			    (double) (yo + wavePtr->height / 2),
			    &fpts[0].x, &fpts[0].y);
    Tk_CanvasDrawableCoords(canvas, (double) (xo + wavePtr->width - 1),
			    (double) (yo + wavePtr->height / 2),
			    &fpts[1].x, &fpts[1].y);
    XDrawLines(display, drawable, wavePtr->gc, fpts, 2, CoordModeOrigin);
  }
  
  if (wavePtr->frame) {
    Tk_CanvasDrawableCoords(canvas, (double) xo, (double) yo,
			    &fpts[0].x, &fpts[0].y);
    Tk_CanvasDrawableCoords(canvas, (double) (xo + wavePtr->width - 1), 
			    (double) yo,
			    &fpts[1].x, &fpts[1].y);
    Tk_CanvasDrawableCoords(canvas, (double) (xo + wavePtr->width - 1),
			    (double) (yo + wavePtr->height - 1),
			    &fpts[2].x, &fpts[2].y);
    Tk_CanvasDrawableCoords(canvas, (double) xo, 
			    (double) (yo + wavePtr->height - 1),
			    &fpts[3].x, &fpts[3].y);
    Tk_CanvasDrawableCoords(canvas, (double) xo, (double) yo,
			    &fpts[4].x, &fpts[4].y);
    XDrawLines(display, drawable, wavePtr->gc, fpts, 5, CoordModeOrigin);
  }

  if (wavePtr->debug > 1) Snack_WriteLog("  Exit DisplayWave\n");
}

static double
WaveToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *coords)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;
  double dx = 0.0, dy = 0.0;
  double x1 = wavePtr->header.x1;
  double y1 = wavePtr->header.y1;
  double x2 = wavePtr->header.x2;
  double y2 = wavePtr->header.y2;
  
  if (coords[0] < x1)
    dx = x1 - coords[0];
  else if (coords[0] > x2)
    dx = coords[0] - x2;
  else
    dx = 0;

  if (coords[1] < y1)
    dy = y1 - coords[1];
  else if (coords[1] > y2)
    dy = coords[1] - y2;
  else
    dy = 0;
  
  return hypot(dx, dy);
}

static int
WaveToArea(Tk_Canvas canvas, Tk_Item *itemPtr, double *rectPtr)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;

  if ((rectPtr[2] <= wavePtr->header.x1) ||
      (rectPtr[0] >= wavePtr->header.x2) ||
      (rectPtr[3] <= wavePtr->header.y1) ||
      (rectPtr[1] >= wavePtr->header.y2))
    return -1;

  if ((rectPtr[0] <= wavePtr->header.x1) &&
      (rectPtr[1] <= wavePtr->header.y1) &&
      (rectPtr[2] >= wavePtr->header.x2) &&
      (rectPtr[3] >= wavePtr->header.y2))
    return 1;
 
  return 0;
}

static void
ScaleWave(Tk_Canvas canvas, Tk_Item *itemPtr, double ox, double oy,
	  double sx, double sy)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;
  double *x0 = wavePtr->x0;
  double *y0 = wavePtr->y0;
  double *x1 = wavePtr->x1;
  double *y1 = wavePtr->y1;
  int i;
  
  for (i = 0; i < wavePtr->width; i++) {
    x0[i] = ox + sx * (x0[i] - ox);
    y0[i] = oy + sy * (y0[i] - oy);
    x1[i] = ox + sx * (x1[i] - ox);
    y1[i] = oy + sy * (y1[i] - oy);
  }
  wavePtr->width  = (int) (sx * wavePtr->width) + 1;
  wavePtr->height = (int) (sy * wavePtr->height);
  if (wavePtr->bufPos > 0)
    wavePtr->pixpsec = (double) wavePtr->width * wavePtr->samprate /
      wavePtr->bufPos;

  ComputeWaveBbox(canvas, wavePtr);
}

static void
TranslateWave(Tk_Canvas canvas, Tk_Item *itemPtr, double dx, double dy)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;

  wavePtr->x += dx;
  wavePtr->y += dy;
  ComputeWaveBbox(canvas, wavePtr);
}

static int
WaveToPS(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass)
{
  WaveItem *wavePtr = (WaveItem *) itemPtr;
  double  *x0 = wavePtr->x0;
  double  *y0 = wavePtr->y0;
  double  *x1 = wavePtr->x1;
  double  *y1 = wavePtr->y1;
  int i;
  char buffer[100];
  int xo = wavePtr->header.x1;
  int yo = wavePtr->header.y1;
  float scale = 1000000.0f;

  if (wavePtr->fg == NULL) {
    return TCL_OK;
  }
  if (wavePtr->height > 2) {
    scale = 2 * ((wavePtr->maxv > -wavePtr->minv)
		 ? wavePtr->maxv :
		 -wavePtr->minv) / (float)(wavePtr->height - 2);
  }
  if (scale < 0.00001f) {
    scale = 0.00001f;
  }

  Tcl_AppendResult(interp, "%% WAVE BEGIN\n", (char *) NULL);
  
  for (i = 0; i < wavePtr->width; i++) {
    sprintf(buffer,
	    "%.1f %.1f moveto\n%.1f %.1f lineto\n",
	    x0[i] + xo, Tk_CanvasPsY(canvas, (double) 
				     (-y0[i]/scale + yo+ wavePtr->height / 2)),
	    x1[i] + xo, Tk_CanvasPsY(canvas, (double) 
				     (-y1[i]/scale + yo+ wavePtr->height / 2)));
    Tcl_AppendResult(interp, buffer, (char *) NULL);
    if ((double)(wavePtr->esmp - wavePtr->ssmp)/wavePtr->width < 1.0) {
      sprintf(buffer, "%.1f %.1f lineto\n",
	      x1[i] + xo + 1, Tk_CanvasPsY(canvas, (double) 
		   (-y1[i]/scale + yo+ wavePtr->height / 2)));
      Tcl_AppendResult(interp, buffer, (char *) NULL);
    }
  }

  if (wavePtr->zeroLevel) {
    sprintf(buffer, "%.1f %.1f moveto\n", (double) xo,
	    Tk_CanvasPsY(canvas, (double) (yo + wavePtr->height / 2)));
    Tcl_AppendResult(interp, buffer, (char *) NULL);

    sprintf(buffer, "%.1f %.1f lineto\n", (double) xo + wavePtr->width - 1,
	    Tk_CanvasPsY(canvas, (double) (yo + wavePtr->height / 2)));
    Tcl_AppendResult(interp, buffer, (char *) NULL);
  }

  if (wavePtr->frame) {
    sprintf(buffer, "%.1f %.1f moveto\n", (double) xo, Tk_CanvasPsY(canvas, (double) yo));
    Tcl_AppendResult(interp, buffer, (char *) NULL);

    sprintf(buffer, "%.1f %.1f lineto\n", (double) xo + wavePtr->width - 1,
	    Tk_CanvasPsY(canvas, (double) yo));
    Tcl_AppendResult(interp, buffer, (char *) NULL);

    sprintf(buffer, "%.1f %.1f lineto\n", (double) xo + wavePtr->width - 1,
	    Tk_CanvasPsY(canvas, (double) (yo + wavePtr->height - 1)));
    Tcl_AppendResult(interp, buffer, (char *) NULL);

    sprintf(buffer, "%.1f %.1f lineto\n", (double) xo,
	    Tk_CanvasPsY(canvas, (double) (yo + wavePtr->height - 1)));
    Tcl_AppendResult(interp, buffer, (char *) NULL);

    sprintf(buffer, "%.1f %.1f lineto\n", (double) xo,
	    Tk_CanvasPsY(canvas, (double) yo));
    Tcl_AppendResult(interp, buffer, (char *) NULL);
  }

  Tcl_AppendResult(interp, "1 setlinewidth\n", (char *) NULL);
  Tcl_AppendResult(interp, "0 setlinecap\n0 setlinejoin\n", (char *) NULL);
  if (Tk_CanvasPsColor(interp, canvas, wavePtr->fg) != TCL_OK) {
    return TCL_ERROR;
  };
  if (wavePtr->fillStipple != None) {
    Tcl_AppendResult(interp, "StrokeClip ", (char *) NULL);
    if (Tk_CanvasPsStipple(interp, canvas, wavePtr->fillStipple) != TCL_OK) {
      return TCL_ERROR;
    }
  } else {
    Tcl_AppendResult(interp, "stroke\n", (char *) NULL);
  }
  
  Tcl_AppendResult(interp, "%% WAVE END\n", (char *) NULL);

  return TCL_OK;
}
