1/*
2 * Copyright (C) 1997-2005 Kare Sjolander <kare@speech.kth.se>
3 *
4 * This file is part of the Snack Sound Toolkit.
5 * The latest version can be found at http://www.speech.kth.se/snack/
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22#include "tcl.h"
23#include "snack.h"
24#include <stdio.h>
25#include <stdlib.h>
26#include <math.h>
27#define USE_OLD_CANVAS /* To keep Tk8.3 happy */
28#include "tk.h"
29#include "jkCanvItems.h"
30#include <string.h>
31
32#define SNACK_DEFAULT_SECTWINTYPE      SNACK_WIN_HAMMING
33#define SNACK_DEFAULT_SECTWINTYPE_NAME "hamming"
34
35#define SNACK_DEFAULT_LPC_ORDER        "20"
36
37/*
38 * Section item structure
39 */
40
41typedef struct SectionItem  {
42
43  Tk_Item header;
44  Tk_Canvas canvas;
45  double x, y;
46  Tk_Anchor anchor;
47  int nPoints;
48  double *coords;
49  XColor *fg;
50  Pixmap fillStipple;
51  GC gc;
52  char *newSoundName;
53  char *soundName;
54  Sound *sound;
55  SnackItemInfo si;
56  float *xfft;
57  double *ffts;
58  int height;
59  int width;
60  int startSmp;
61  int endSmp;
62  int ssmp;
63  int esmp;
64  int frame;
65  int id;
66  XPoint fpts[5];
67  char *channelstr;
68  int debug;
69  double topFrequency;
70  double maxValue;
71  double minValue;
72  char *windowTypeStr;
73  char *analysisTypeStr;
74  int type;
75  int lpcOrder;
76  Tcl_Interp *interp;
77  double preemph;
78
79} SectionItem;
80
81Tk_CustomOption sectTagsOption = { (Tk_OptionParseProc *) NULL,
82				   (Tk_OptionPrintProc *) NULL,
83				   (ClientData) NULL };
84
85typedef enum {
86  OPTION_ANCHOR,
87  OPTION_TAGS,
88  OPTION_SOUND,
89  OPTION_HEIGHT,
90  OPTION_WIDTH,
91  OPTION_FFTLEN,
92  OPTION_WINLEN,
93  OPTION_PREEMP,
94  OPTION_START,
95  OPTION_END,
96  OPTION_FILL,
97  OPTION_STIPPLE,
98  OPTION_FRAME,
99  OPTION_TOPFREQUENCY,
100  OPTION_CHANNEL,
101  OPTION_MAXVAL,
102  OPTION_MINVAL,
103  OPTION_SKIP,
104  OPTION_WINTYPE,
105  OPTION_ANALYSISTYPE,
106  OPTION_LPCORDER
107} ConfigSpec;
108
109static Tk_ConfigSpec configSpecs[] = {
110
111  {TK_CONFIG_ANCHOR, "-anchor", (char *) NULL, (char *) NULL,
112   "nw", Tk_Offset(SectionItem, anchor), TK_CONFIG_DONT_SET_DEFAULT},
113
114  {TK_CONFIG_CUSTOM, "-tags", (char *) NULL, (char *) NULL,
115   (char *) NULL, 0, TK_CONFIG_NULL_OK, &sectTagsOption},
116
117  {TK_CONFIG_STRING, "-sound", (char *) NULL, (char *) NULL,
118   "", Tk_Offset(SectionItem, newSoundName), TK_CONFIG_NULL_OK},
119
120  {TK_CONFIG_INT, "-height", (char *) NULL, (char *) NULL,
121   "256", Tk_Offset(SectionItem, height), 0},
122
123  {TK_CONFIG_PIXELS, "-width", (char *) NULL, (char *) NULL,
124   "256", Tk_Offset(SectionItem, width), 0},
125
126  {TK_CONFIG_INT, "-fftlength", (char *) NULL, (char *) NULL,
127   "512", Tk_Offset(SectionItem, si.fftlen), 0},
128
129  {TK_CONFIG_INT, "-winlength", (char *) NULL, (char *) NULL,
130   "256", Tk_Offset(SectionItem, si.winlen), 0},
131
132  {TK_CONFIG_DOUBLE, "-preemphasisfactor", (char *) NULL, (char *) NULL,
133   "0.0", Tk_Offset(SectionItem, preemph), 0},
134
135  {TK_CONFIG_INT, "-start", (char *) NULL, (char *) NULL,
136   "0", Tk_Offset(SectionItem, startSmp), 0},
137
138  {TK_CONFIG_INT, "-end", (char *) NULL, (char *) NULL,
139   "-1", Tk_Offset(SectionItem, endSmp), 0},
140
141  {TK_CONFIG_COLOR, "-fill", (char *) NULL, (char *) NULL,
142   "black", Tk_Offset(SectionItem, fg), TK_CONFIG_NULL_OK},
143
144  {TK_CONFIG_BITMAP, "-stipple", (char *) NULL, (char *) NULL,
145   (char *) NULL, Tk_Offset(SectionItem, fillStipple), TK_CONFIG_NULL_OK},
146
147  {TK_CONFIG_BOOLEAN, "-frame", (char *) NULL, (char *) NULL,
148   "no", Tk_Offset(SectionItem, frame), TK_CONFIG_NULL_OK},
149
150  {TK_CONFIG_DOUBLE, "-topfrequency", (char *) NULL, (char *) NULL,
151   "0.0", Tk_Offset(SectionItem, topFrequency), 0},
152
153  {TK_CONFIG_STRING, "-channel", (char *) NULL, (char *) NULL,
154   "-1", Tk_Offset(SectionItem, channelstr), TK_CONFIG_NULL_OK},
155
156  {TK_CONFIG_DOUBLE, "-maxvalue", (char *) NULL, (char *) NULL,
157   "0.0", Tk_Offset(SectionItem, maxValue), 0},
158
159  {TK_CONFIG_DOUBLE, "-minvalue", (char *) NULL, (char *) NULL,
160   "-80.0", Tk_Offset(SectionItem, minValue), 0},
161
162  {TK_CONFIG_INT, "-skip", (char *) NULL, (char *) NULL,
163   "-1", Tk_Offset(SectionItem, si.skip), 0},
164
165  {TK_CONFIG_STRING, "-windowtype", (char *) NULL, (char *) NULL,
166   SNACK_DEFAULT_SECTWINTYPE_NAME, Tk_Offset(SectionItem, windowTypeStr), 0},
167
168  {TK_CONFIG_STRING, "-analysistype", (char *) NULL, (char *) NULL,
169   "fft", Tk_Offset(SectionItem, analysisTypeStr), 0},
170
171  {TK_CONFIG_INT, "-lpcorder", (char *) NULL, (char *) NULL,
172   SNACK_DEFAULT_LPC_ORDER, Tk_Offset(SectionItem, lpcOrder), 0},
173
174  {TK_CONFIG_INT, "-debug", (char *) NULL, (char *) NULL,
175   "0", Tk_Offset(SectionItem, debug), 0},
176
177  {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
178   (char *) NULL, 0, 0}
179
180};
181
182/*
183 * Protos
184 */
185
186static void   ComputeSectionBbox(Tk_Canvas canvas, SectionItem *sectPtr);
187
188static int    ComputeSectionCoords(Tk_Item *itemPtr);
189
190static int    ConfigureSection(Tcl_Interp *interp, Tk_Canvas canvas,
191			       Tk_Item *itemPtr, int argc,
192			       char **argv, int flags);
193
194static int    CreateSection(Tcl_Interp *interp,	Tk_Canvas canvas,
195			    struct Tk_Item *itemPtr, int argc, char **argv);
196
197static void   DeleteSection(Tk_Canvas canvas, Tk_Item *itemPtr,
198			    Display *display);
199
200static void   DisplaySection(Tk_Canvas canvas, Tk_Item *itemPtr,
201			     Display *display, Drawable dst,
202			     int x, int y, int width, int height);
203
204static void   ScaleSection(Tk_Canvas canvas, Tk_Item *itemPtr,
205			   double originX, double originY,
206			   double scaleX, double scaleY);
207
208static int    SectionCoords(Tcl_Interp *interp,	Tk_Canvas canvas,
209			    Tk_Item *itemPtr, int argc, char **argv);
210
211static int    SectionToArea(Tk_Canvas canvas, Tk_Item *itemPtr,
212			    double *rectPtr);
213
214static double SectionToPoint(Tk_Canvas canvas, Tk_Item *itemPtr,
215			     double *coords);
216
217static int    SectionToPS(Tcl_Interp *interp, Tk_Canvas canvas,
218			  Tk_Item *itemPtr, int prepass);
219
220static void   TranslateSection(Tk_Canvas canvas, Tk_Item *itemPtr,
221			       double deltaX, double deltaY);
222
223/*
224 * Section item type
225 */
226
227Tk_ItemType snackSectionType = {
228  "section",
229  sizeof(SectionItem),
230  CreateSection,
231  configSpecs,
232  ConfigureSection,
233  SectionCoords,
234  DeleteSection,
235  DisplaySection,
236  0,
237  SectionToPoint,
238  SectionToArea,
239  SectionToPS,
240  ScaleSection,
241  TranslateSection,
242  (Tk_ItemIndexProc *) NULL,
243  (Tk_ItemCursorProc *) NULL,
244  (Tk_ItemSelectionProc *) NULL,
245  (Tk_ItemInsertProc *) NULL,
246  (Tk_ItemDCharsProc *) NULL,
247  (Tk_ItemType *) NULL
248};
249
250static int
251CreateSection(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr,
252	   int argc, char **argv)
253{
254  SectionItem *sectPtr = (SectionItem *) itemPtr;
255
256  if (argc < 2) {
257    Tcl_AppendResult(interp, "wrong # args: should be \"",
258		     Tk_PathName(Tk_CanvasTkwin(canvas)), " create ",
259		     itemPtr->typePtr->name, " x y ?opts?\"", (char *) NULL);
260    return TCL_ERROR;
261  }
262
263  sectPtr->canvas = canvas;
264  sectPtr->anchor = TK_ANCHOR_NW;
265  sectPtr->nPoints = 0;
266  sectPtr->coords = NULL;
267  sectPtr->fg = None;
268  sectPtr->fillStipple = None;
269  sectPtr->gc = None;
270  sectPtr->newSoundName = NULL;
271  sectPtr->soundName = NULL;
272  sectPtr->sound = NULL;
273  sectPtr->si.samprate = 16000;
274  sectPtr->si.BufPos = 0;
275  sectPtr->si.fftlen = 512;
276  sectPtr->si.winlen = 256;
277  sectPtr->preemph = 0.0;
278  sectPtr->si.hamwin = (float *) ckalloc(NMAX * sizeof(float));
279  sectPtr->si.abmax = 0.0f;
280  sectPtr->xfft = (float *)  ckalloc(NMAX * sizeof(float));
281  sectPtr->ffts = (double *) ckalloc(NMAX / 2 * sizeof(double));
282  sectPtr->height = 256;
283  sectPtr->width = 256;
284  sectPtr->startSmp = 0;
285  sectPtr->endSmp = -1;
286  sectPtr->ssmp = 0;
287  sectPtr->esmp = -1;
288  sectPtr->id = 0;
289  sectPtr->frame = 0;
290  sectPtr->debug = 0;
291  sectPtr->x = 0;
292  sectPtr->y = 0;
293  sectPtr->topFrequency = 0.0;
294  sectPtr->channelstr = NULL;
295  sectPtr->si.channel = -1;
296  sectPtr->si.channelSet = -1;
297  sectPtr->si.nchannels = 1;
298  sectPtr->maxValue = 0.0;
299  sectPtr->minValue = -80.0;
300  sectPtr->si.validStart = 0;
301  sectPtr->si.skip = -1;
302  sectPtr->si.windowType = SNACK_DEFAULT_SECTWINTYPE;
303  sectPtr->si.windowTypeSet = SNACK_DEFAULT_SECTWINTYPE;
304  sectPtr->windowTypeStr = NULL;
305  sectPtr->analysisTypeStr = NULL;
306  sectPtr->type = 0;
307  sectPtr->lpcOrder = atoi(SNACK_DEFAULT_LPC_ORDER);
308  sectPtr->interp = interp;
309
310  if (sectPtr->si.hamwin == NULL) {
311    Tcl_AppendResult(interp, "Couldn't allocate analysis window buffer!",NULL);
312    return TCL_ERROR;
313  }
314
315  if (sectPtr->xfft == NULL) {
316    Tcl_AppendResult(interp, "Couldn't allocate fft buffer!", NULL);
317    ckfree((char *)sectPtr->si.hamwin);
318    return TCL_ERROR;
319  }
320
321  if (sectPtr->ffts == NULL) {
322    Tcl_AppendResult(interp, "Couldn't allocate fft buffer!", NULL);
323    ckfree((char *)sectPtr->si.hamwin);
324    ckfree((char *)sectPtr->xfft);
325    return TCL_ERROR;
326  }
327
328
329  if ((Tk_CanvasGetCoord(interp, canvas, argv[0], &sectPtr->x) != TCL_OK) ||
330      (Tk_CanvasGetCoord(interp, canvas, argv[1], &sectPtr->y) != TCL_OK))
331    return TCL_ERROR;
332
333  if (ConfigureSection(interp, canvas, itemPtr, argc-2, argv+2, 0) == TCL_OK)
334    return TCL_OK;
335
336  DeleteSection(canvas, itemPtr, Tk_Display(Tk_CanvasTkwin(canvas)));
337  return TCL_ERROR;
338}
339
340static int
341SectionCoords(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr,
342	      int argc, char **argv)
343{
344  SectionItem *wPtr = (SectionItem *) itemPtr;
345  char xc[TCL_DOUBLE_SPACE], yc[TCL_DOUBLE_SPACE];
346
347  if (argc == 0) {
348    Tcl_PrintDouble(interp, wPtr->x, xc);
349    Tcl_PrintDouble(interp, wPtr->y, yc);
350    Tcl_AppendResult(interp, xc, " ", yc, (char *) NULL);
351  } else if (argc == 2) {
352    if ((Tk_CanvasGetCoord(interp, canvas, argv[0], &wPtr->x) != TCL_OK) ||
353	(Tk_CanvasGetCoord(interp, canvas, argv[1], &wPtr->y) != TCL_OK)) {
354      return TCL_ERROR;
355    }
356    ComputeSectionBbox(canvas, wPtr);
357  } else {
358    char buf[80];
359
360    sprintf(buf, "wrong # coordinates: expected 0 or 2, got %d", argc);
361    Tcl_SetResult(interp, buf, TCL_VOLATILE);
362
363    return TCL_ERROR;
364  }
365  return TCL_OK;
366}
367
368static int
369ComputeSectionCoords(Tk_Item *itemPtr)
370{
371  SectionItem *sectPtr = (SectionItem *) itemPtr;
372  int i;
373  int nPoints = sectPtr->nPoints;
374  float xscale = (float) (sectPtr->width) / nPoints;
375  float yscale = (float) ((float) (sectPtr->height - 1) /
376    (sectPtr->minValue - sectPtr->maxValue));
377  float fscale = (float) (sectPtr->si.topfrequency / (sectPtr->si.samprate / 2.0));
378
379  if (sectPtr->debug > 1) {
380    Snack_WriteLogInt("  Enter ComputeSectionCoords", nPoints);
381  }
382
383  if (sectPtr->coords != NULL) ckfree((char *) sectPtr->coords);
384  sectPtr->coords = (double *) ckalloc((unsigned)
385			       (sizeof(double) * (2 * nPoints)));
386
387  for (i = 0; i < nPoints; i++) {
388    double t = (double) (sectPtr->ffts[(int)((float)i*fscale)] -
389			 sectPtr->maxValue) * yscale;
390    if (t > sectPtr->height-1) t = (double) (sectPtr->height-1);
391    if (t < 0.0) t = 0.0;
392    sectPtr->coords[i*2]   = (double) i * xscale;
393    sectPtr->coords[i*2+1] = t;
394  }
395
396  ComputeSectionBbox(sectPtr->canvas, sectPtr);
397
398  if (sectPtr->debug) Snack_WriteLog("Exit ComputeSectionCoords\n");
399
400  return TCL_OK;
401}
402
403void
404GetFloatMonoSigSect(SnackItemInfo *siPtr,SnackLinkedFileInfo *info,
405		    float *sig,int beg, int len) {
406  /* sig buffer must be allocated, file must be open! */
407
408  int i;
409
410  if (siPtr->storeType == SOUND_IN_MEMORY) {
411    if (siPtr->nchannels == 1 || siPtr->channel != -1) {
412      int p = beg * siPtr->nchannels + siPtr->channel;
413
414      for (i = 0; i < len; i++) {
415	sig[i] = (float) (FSAMPLE(siPtr, p));
416	p += siPtr->nchannels;
417      }
418    } else {
419      int c;
420
421      for (i = 0; i < len; i++) {
422	sig[i] = 0.0;
423      }
424      for (c = 0; c < siPtr->nchannels; c++) {
425	int p = beg * siPtr->nchannels + c;
426
427	for (i = 0; i < len; i++) {
428	  sig[i] += (float) (FSAMPLE(siPtr, p));
429	  p += siPtr->nchannels;
430	}
431      }
432      for (i = 0; i < len; i++) {
433	sig[i] /= siPtr->nchannels;
434      }
435    }
436  } else { /* storeType != SOUND_IN_MEMORY */
437    if (siPtr->nchannels == 1 || siPtr->channel != -1) {
438      int p = beg * siPtr->nchannels + siPtr->channel;
439
440      for (i = 0; i < len; i++) {
441	sig[i] = (float) (GetSample(info, p));
442	p += siPtr->nchannels;
443      }
444    } else {
445      int c;
446
447      for (i = 0; i < len; i++) {
448	sig[i] = 0.0;
449      }
450      for (c = 0; c < siPtr->nchannels; c++) {
451	int p = beg * siPtr->nchannels + c;
452
453	for (i = 0; i < len; i++) {
454	  sig[i] += (float) (GetSample(info, p));
455	  p += siPtr->nchannels;
456	}
457      }
458      for (i = 0; i < len; i++) {
459	sig[i] /= siPtr->nchannels;
460      }
461    }
462  }
463}
464
465extern void Snack_PowerSpectrum(float *z);
466
467void
468ComputeSection(Tk_Item *itemPtr)
469{
470  SectionItem *sectPtr = (SectionItem *) itemPtr;
471  SnackItemInfo *siPtr = &sectPtr->si;
472  int i, j;
473  int fftlen     = siPtr->fftlen;
474  int winlen     = siPtr->winlen;
475  float preemph  = siPtr->preemph;
476  int RestartPos = siPtr->RestartPos - siPtr->validStart;
477  int storeType  = siPtr->storeType;
478  int n, skip = siPtr->skip;
479  SnackLinkedFileInfo info;
480  float *sig_lpc;
481  float presample = 0.0;
482  int siglen;
483  float g_lpc;
484
485  if (sectPtr->debug) Snack_WriteLogInt("Enter ComputeSection", sectPtr->ssmp);
486
487  if (skip < 1) {
488    skip = fftlen;
489  }
490  siglen = sectPtr->esmp - siPtr->RestartPos;
491  n = siglen / skip;
492
493  for (i = 0; i < fftlen/2; i++) {
494    sectPtr->ffts[i] = 0.0;
495  }
496
497  if (n == 0) return;
498
499  Snack_InitFFT(fftlen);
500  Snack_InitWindow(siPtr->hamwin, winlen, fftlen, siPtr->windowType);
501
502  if (storeType != SOUND_IN_MEMORY) {
503    if (OpenLinkedFile(sectPtr->sound, &info) != TCL_OK) {
504      return;
505    }
506  }
507
508  if (sectPtr->type != 0 && n > 0) { /* LPC + FFT */
509
510    sig_lpc = (float *) ckalloc(siglen * sizeof(float));
511
512    GetFloatMonoSigSect(siPtr,&info,sig_lpc,RestartPos,siglen);
513    if (RestartPos > 0)
514	GetFloatMonoSigSect(siPtr,&info,&presample,RestartPos-1,1);
515    PreEmphase(sig_lpc,presample,siglen,preemph);
516
517    /* windowing signal to make lpc look more like the fft spectrum ??? */
518    for (i = 0; i < winlen/2; i++) {
519      sig_lpc[i] = sig_lpc[i] * siPtr->hamwin[i];
520    }
521    for (i = winlen/2; i < winlen; i++) {
522      sig_lpc[i+siglen-winlen] = sig_lpc[i+siglen-winlen] * siPtr->hamwin[i];
523    }
524
525    g_lpc = LpcAnalysis(sig_lpc,siglen,sectPtr->xfft,sectPtr->lpcOrder);
526    ckfree((char *)sig_lpc);
527
528    for (i=0; i<=sectPtr->lpcOrder; i++) {
529      /* the factor is a guess, try looking for analytical value */
530      sectPtr->xfft[i] = sectPtr->xfft[i] * 5000000000.0f;
531    }
532    for (i = sectPtr->lpcOrder + 1; i < fftlen; i++) {
533      sectPtr->xfft[i] = 0.0;
534    }
535
536    Snack_DBPowerSpectrum(sectPtr->xfft);
537
538    for (i = 0; i < fftlen/2; i++) {
539      sectPtr->ffts[i] = -sectPtr->xfft[i];
540    }
541  } else {  /* usual FFT */
542
543    for (j = 0; j < n; j++) {
544      if (storeType == SOUND_IN_MEMORY) {
545	if (siPtr->nchannels == 1 || siPtr->channel != -1) {
546	  int p = (RestartPos + j * skip) * siPtr->nchannels + siPtr->channel;
547
548	  for (i = 0; i < fftlen; i++) {
549	    sectPtr->xfft[i] = (float) ((FSAMPLE(siPtr, p + siPtr->nchannels)
550					 - preemph * FSAMPLE(siPtr, p))
551					* siPtr->hamwin[i]);
552	    p += siPtr->nchannels;
553	  }
554	} else {
555	  int c;
556
557	  for (i = 0; i < fftlen; i++) {
558	    sectPtr->xfft[i] = 0.0;
559	  }
560	  for (c = 0; c < siPtr->nchannels; c++) {
561	    int p = (RestartPos + j * skip) * siPtr->nchannels + c;
562
563	    for (i = 0; i < fftlen; i++) {
564	      sectPtr->xfft[i] += (float)((FSAMPLE(siPtr, p + siPtr->nchannels)
565					   - preemph * FSAMPLE(siPtr, p))
566					  * siPtr->hamwin[i]);
567	      p += siPtr->nchannels;
568	    }
569	  }
570	  for (i = 0; i < fftlen; i++) {
571	    sectPtr->xfft[i] /= siPtr->nchannels;
572	  }
573	}
574      } else { /* storeType != SOUND_IN_MEMORY */
575	if (siPtr->nchannels == 1 || siPtr->channel != -1) {
576	  int p = (RestartPos + j * skip) * siPtr->nchannels + siPtr->channel;
577
578	  for (i = 0; i < fftlen; i++) {
579	    sectPtr->xfft[i] = (float) ((GetSample(&info, p + siPtr->nchannels)
580					 - preemph * GetSample(&info, p))
581					* siPtr->hamwin[i]);
582	    p += siPtr->nchannels;
583	  }
584	} else {
585	  int c;
586
587	  for (i = 0; i < fftlen; i++) {
588	    sectPtr->xfft[i] = 0.0;
589	  }
590	  for (c = 0; c < siPtr->nchannels; c++) {
591	    int p = (RestartPos + j * skip) * siPtr->nchannels + c;
592
593	    for (i = 0; i < fftlen; i++) {
594	      sectPtr->xfft[i] += (float)((GetSample(&info, p+siPtr->nchannels)
595					   - preemph * GetSample(&info, p))
596					  * siPtr->hamwin[i]);
597	      p += siPtr->nchannels;
598	    }
599	  }
600	  for (i = 0; i < fftlen; i++) {
601	    sectPtr->xfft[i] /= siPtr->nchannels;
602	  }
603	}
604      }
605
606      Snack_PowerSpectrum(sectPtr->xfft);
607
608      for (i = 0; i < fftlen/2; i++) {
609	sectPtr->ffts[i] += sectPtr->xfft[i];
610      }
611    }
612
613    for (i = 0; i < fftlen/2; i++) {
614      sectPtr->ffts[i] = sectPtr->ffts[i] / (float) n;
615    }
616
617    for (i = 1; i < fftlen/2; i++) {
618      if (sectPtr->ffts[i] < SNACK_INTLOGARGMIN)
619	sectPtr->ffts[i] = SNACK_INTLOGARGMIN;
620      sectPtr->ffts[i] = (float)(SNACK_DB*log(sectPtr->ffts[i]) - SNACK_CORRN);
621    }
622    if (sectPtr->ffts[0] < SNACK_INTLOGARGMIN)
623      sectPtr->ffts[0] = SNACK_INTLOGARGMIN;
624    sectPtr->ffts[0] = (float)(SNACK_DB*log(sectPtr->ffts[0]) - SNACK_CORR0);
625  }
626  if (storeType != SOUND_IN_MEMORY) {
627    CloseLinkedFile(&info);
628  }
629
630  if (sectPtr->debug) Snack_WriteLog("Exit ComputeSection");
631}
632
633static void
634UpdateSection(ClientData clientData, int flag)
635{
636  SectionItem *sectPtr = (SectionItem *) clientData;
637  Sound *s = sectPtr->sound;
638
639  if (sectPtr->debug) Snack_WriteLogInt("Enter UpdateSection", flag);
640
641  if (sectPtr->canvas == NULL) return;
642
643  if (flag == SNACK_DESTROY_SOUND) {
644    sectPtr->sound = NULL;
645    if (sectPtr->id) Snack_RemoveCallback(s, sectPtr->id);
646    sectPtr->id = 0;
647    return;
648  }
649
650  Tk_CanvasEventuallyRedraw(sectPtr->canvas,
651			    sectPtr->header.x1, sectPtr->header.y1,
652			    sectPtr->header.x2, sectPtr->header.y2);
653
654  sectPtr->si.blocks = s->blocks;
655  sectPtr->si.BufPos = s->length;
656  sectPtr->si.storeType = s->storeType;
657  sectPtr->si.samprate = s->samprate;
658  sectPtr->si.encoding = s->encoding;
659  sectPtr->si.nchannels = s->nchannels;
660
661  if (flag == SNACK_MORE_SOUND) {
662    sectPtr->esmp = sectPtr->si.BufPos - 1;
663    sectPtr->ssmp = sectPtr->esmp - sectPtr->si.fftlen;
664
665    if (sectPtr->ssmp < 0) {
666      sectPtr->ssmp = 0;
667    }
668
669    sectPtr->si.RestartPos = sectPtr->ssmp;
670  }
671
672  if (flag == SNACK_NEW_SOUND) {
673    sectPtr->esmp = sectPtr->endSmp;
674
675    if (sectPtr->endSmp < 0)
676      sectPtr->esmp = sectPtr->si.BufPos - 1;
677
678    if (sectPtr->endSmp > sectPtr->si.BufPos - 1)
679      sectPtr->esmp = sectPtr->si.BufPos - 1;
680
681    if (sectPtr->startSmp > sectPtr->endSmp && sectPtr->endSmp >= 0)
682      sectPtr->startSmp = sectPtr->endSmp;
683
684    if (sectPtr->startSmp < 0)
685      sectPtr->startSmp = 0;
686
687    sectPtr->ssmp = sectPtr->startSmp;
688
689    if (sectPtr->ssmp > sectPtr->esmp)
690      sectPtr->ssmp = sectPtr->esmp;
691
692    if (sectPtr->ssmp > sectPtr->esmp - sectPtr->si.fftlen) {
693      sectPtr->esmp = sectPtr->ssmp + sectPtr->si.fftlen;
694      if (sectPtr->esmp > sectPtr->si.BufPos - 1) {
695	sectPtr->esmp = sectPtr->si.BufPos - 1;
696	sectPtr->ssmp = sectPtr->esmp - sectPtr->si.fftlen;
697	if (sectPtr->ssmp < 0) {
698	  sectPtr->ssmp = 0;
699	}
700      }
701    }
702
703    if (sectPtr->topFrequency <= 0.0) {
704      sectPtr->si.topfrequency = sectPtr->si.samprate / 2.0;
705    } else if (sectPtr->topFrequency > sectPtr->si.samprate / 2.0) {
706      sectPtr->si.topfrequency = sectPtr->si.samprate / 2.0;
707    } else {
708      sectPtr->si.topfrequency = sectPtr->topFrequency;
709    }
710  }
711  sectPtr->si.channel = sectPtr->si.channelSet;
712  if (sectPtr->si.nchannels == 1) {
713    sectPtr->si.channel = 0;
714  }
715
716  sectPtr->si.validStart = s->validStart;
717
718  ComputeSection((Tk_Item *)sectPtr);
719
720  if (ComputeSectionCoords((Tk_Item *)sectPtr) != TCL_OK) {
721    return;
722  }
723
724  Tk_CanvasEventuallyRedraw(sectPtr->canvas,
725			    sectPtr->header.x1, sectPtr->header.y1,
726			    sectPtr->header.x2, sectPtr->header.y2);
727
728  if (sectPtr->debug) Snack_WriteLog("Exit UpdateSection\n");
729}
730
731static int
732ConfigureSection(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr,
733	      int argc, char **argv, int flags)
734{
735  SectionItem *sectPtr = (SectionItem *) itemPtr;
736  Sound *s = sectPtr->sound;
737  Tk_Window tkwin = Tk_CanvasTkwin(canvas);
738  XGCValues gcValues;
739  GC newGC;
740  unsigned long mask;
741  int doCompute = 0;
742  int i, j;
743
744  if (argc == 0) return TCL_OK;
745
746  if (Tk_ConfigureWidget(interp, tkwin, configSpecs, argc,
747			 (CONST84 char **)argv,
748			 (char *) sectPtr, flags) != TCL_OK) return TCL_ERROR;
749
750  if (sectPtr->debug) Snack_WriteLog("Enter ConfigureSection\n");
751
752  for (i = 0; configSpecs[i].type != TK_CONFIG_END; i++) {
753    for (j = 0; j < argc; j += 2) {
754      if (strncmp(argv[j], configSpecs[i].argvName, strlen(argv[j])) == 0) {
755	configSpecs[i].specFlags |= TK_CONFIG_OPTION_SPECIFIED;
756	break;
757      }
758    }
759  }
760
761#if defined(MAC) || defined(MAC_OSX_TCL)
762  for (i = 0; i < argc; i++) {
763    if (strncmp(argv[i], "-anchor", strlen(argv[i])) == 0) {
764      i++;
765      if (strcmp(argv[i], "ne") == 0) {
766	sectPtr->anchor = 1;
767      } else if (strcmp(argv[i], "nw") == 0) {
768	sectPtr->anchor = 7;
769      } else if (strcmp(argv[i], "n") == 0) {
770	sectPtr->anchor = 0;
771      } else if (strcmp(argv[i], "e") == 0) {
772	sectPtr->anchor = 2;
773      } else if (strcmp(argv[i], "se") == 0) {
774	sectPtr->anchor = 3;
775      } else if (strcmp(argv[i], "sw") == 0) {
776	sectPtr->anchor = 5;
777      } else if (strcmp(argv[i], "s") == 0) {
778	sectPtr->anchor = 4;
779      } else if (strcmp(argv[i], "w") == 0) {
780	sectPtr->anchor = 6;
781      } else if (strncmp(argv[i], "center", strlen(argv[i])) == 0) {
782	sectPtr->anchor = 8;
783      }
784      break;
785    }
786  }
787#endif
788
789  if (CheckFFTlen(interp, sectPtr->si.fftlen) != TCL_OK) return TCL_ERROR;
790
791  if (CheckWinlen(interp, sectPtr->si.winlen, sectPtr->si.fftlen) != TCL_OK)
792    return TCL_ERROR;
793
794  if (CheckLPCorder(interp, sectPtr->lpcOrder) != TCL_OK) return TCL_ERROR;
795
796  if (OptSpecified(OPTION_SOUND)) {
797    if (sectPtr->newSoundName == NULL) {
798      sectPtr->sound = NULL;
799      if (sectPtr->id) Snack_RemoveCallback(s, sectPtr->id);
800      sectPtr->id = 0;
801      sectPtr->si.BufPos = 0;
802      doCompute = 1;
803    } else {
804      if ((s = Snack_GetSound(interp, sectPtr->newSoundName)) == NULL) {
805	return TCL_ERROR;
806      }
807      if (s->storeType == SOUND_IN_CHANNEL) {
808	Tcl_AppendResult(interp, sectPtr->newSoundName,
809			 " can not be linked to a channel", (char *) NULL);
810	return TCL_ERROR;
811      }
812      if (s->storeType == SOUND_IN_FILE) {
813	s->itemRefCnt++;
814      }
815      sectPtr->sound = s;
816      if (sectPtr->soundName == NULL) {
817	sectPtr->soundName = ckalloc(strlen(sectPtr->newSoundName)+1);
818	strcpy(sectPtr->soundName, sectPtr->newSoundName);
819      }
820      if (strcmp(sectPtr->soundName, sectPtr->newSoundName) != 0) {
821	Sound *t = Snack_GetSound(interp, sectPtr->soundName);
822	ckfree(sectPtr->soundName);
823	sectPtr->soundName = ckalloc(strlen(sectPtr->newSoundName)+1);
824	strcpy(sectPtr->soundName, sectPtr->newSoundName);
825	sectPtr->nPoints = 0;
826	sectPtr->ssmp    = 0;
827	sectPtr->esmp    = -1;
828	Snack_RemoveCallback(t, sectPtr->id);
829	sectPtr->id = 0;
830      }
831      if (!sectPtr->id)
832	sectPtr->id = Snack_AddCallback(s, UpdateSection, (int *)sectPtr);
833
834      sectPtr->si.blocks = s->blocks;
835      sectPtr->si.BufPos = s->length;
836      sectPtr->si.samprate = s->samprate;
837      sectPtr->si.encoding = s->encoding;
838      sectPtr->si.nchannels = s->nchannels;
839      sectPtr->si.storeType = s->storeType;
840      doCompute = 1;
841    }
842  }
843  sectPtr->esmp = sectPtr->endSmp;
844
845  if (sectPtr->endSmp < 0)
846    sectPtr->esmp = sectPtr->si.BufPos - 1;
847
848  if (sectPtr->endSmp > sectPtr->si.BufPos - 1)
849    sectPtr->esmp = sectPtr->si.BufPos - 1;
850
851  if (sectPtr->startSmp > sectPtr->endSmp && sectPtr->endSmp >= 0)
852    sectPtr->startSmp = sectPtr->endSmp;
853
854  if (sectPtr->startSmp < 0)
855    sectPtr->startSmp = 0;
856
857  sectPtr->ssmp = sectPtr->startSmp;
858
859  if (sectPtr->ssmp > sectPtr->esmp)
860    sectPtr->ssmp = sectPtr->esmp;
861
862  if (sectPtr->ssmp > sectPtr->esmp - sectPtr->si.fftlen) {
863    sectPtr->esmp = sectPtr->ssmp + sectPtr->si.fftlen;
864    if (sectPtr->esmp > sectPtr->si.BufPos - 1) {
865      sectPtr->esmp = sectPtr->si.BufPos - 1;
866      sectPtr->ssmp = sectPtr->esmp - sectPtr->si.fftlen;
867      if (sectPtr->ssmp < 0) {
868	sectPtr->ssmp = 0;
869      }
870    }
871  }
872
873  if (OptSpecified(OPTION_WINLEN))
874    doCompute = 1;
875
876  if (OptSpecified(OPTION_FFTLEN)) {
877    doCompute = 1;
878  }
879
880  sectPtr->si.preemph = (float) sectPtr->preemph;
881
882  if (OptSpecified(OPTION_SKIP)) {
883    doCompute = 1;
884  }
885
886  if (OptSpecified(OPTION_START)) {
887    doCompute = 1;
888  }
889
890  if (OptSpecified(OPTION_END)) {
891    doCompute = 1;
892  }
893
894  if (sectPtr->topFrequency <= 0.0) {
895    sectPtr->si.topfrequency = sectPtr->si.samprate / 2.0;
896  } else if (sectPtr->topFrequency > sectPtr->si.samprate / 2.0) {
897    sectPtr->si.topfrequency = sectPtr->si.samprate / 2.0;
898  } else {
899    sectPtr->si.topfrequency = sectPtr->topFrequency;
900  }
901
902  if (OptSpecified(OPTION_CHANNEL)) {
903    if (GetChannel(interp, sectPtr->channelstr, sectPtr->si.nchannels,
904		   &sectPtr->si.channelSet) != TCL_OK) {
905      return TCL_ERROR;
906    }
907    doCompute = 1;
908  }
909  sectPtr->si.channel = sectPtr->si.channelSet;
910  if (sectPtr->si.nchannels == 1) {
911    sectPtr->si.channel = 0;
912  }
913
914  if (OptSpecified(OPTION_ANALYSISTYPE)) {
915    int len = strlen(sectPtr->analysisTypeStr);
916
917    if (strncasecmp(sectPtr->analysisTypeStr, "lpc", len) == 0) {
918      sectPtr->type = 1;
919    } else if (strncasecmp(sectPtr->analysisTypeStr, "fft", len) == 0) {
920      sectPtr->type = 0;
921    } else {
922      Tcl_AppendResult(interp, "-type should be FFT or LPC", (char *) NULL);
923      return TCL_ERROR;
924    }
925    doCompute = 1;
926  }
927  if (OptSpecified(OPTION_LPCORDER)) {
928    doCompute = 1;
929  }
930  if (OptSpecified(OPTION_WINTYPE)) {
931    if (GetWindowType(interp, sectPtr->windowTypeStr,
932		      &sectPtr->si.windowTypeSet)
933	!= TCL_OK) {
934      return TCL_ERROR;
935    }
936    doCompute = 1;
937  }
938  sectPtr->si.windowType = sectPtr->si.windowTypeSet;
939
940  if (doCompute) {
941    sectPtr->nPoints = sectPtr->si.fftlen / 2;
942    sectPtr->si.RestartPos = sectPtr->ssmp;
943    ComputeSection((Tk_Item *)sectPtr);
944  }
945
946  if (sectPtr->height <= 2) sectPtr->height = 0;
947
948  if (sectPtr->fg == NULL) {
949    newGC = None;
950  } else {
951    gcValues.foreground = sectPtr->fg->pixel;
952    gcValues.line_width = 1;
953    mask = GCForeground|GCLineWidth;
954    if (sectPtr->fillStipple != None) {
955      gcValues.stipple = sectPtr->fillStipple;
956      gcValues.fill_style = FillStippled;
957      mask |= GCStipple|GCFillStyle;
958    }
959    newGC = Tk_GetGC(tkwin, mask, &gcValues);
960    gcValues.line_width = 0;
961  }
962  if (sectPtr->gc != None) {
963    Tk_FreeGC(Tk_Display(tkwin), sectPtr->gc);
964  }
965  sectPtr->gc = newGC;
966
967  ComputeSectionBbox(canvas, sectPtr);
968
969  if (ComputeSectionCoords(itemPtr) != TCL_OK) {
970    return TCL_ERROR;
971  }
972
973  for (i = 0; configSpecs[i].type != TK_CONFIG_END; i++) {
974    configSpecs[i].specFlags &= ~TK_CONFIG_OPTION_SPECIFIED;
975  }
976
977  if (sectPtr->debug) Snack_WriteLog("Exit ConfigureSection\n");
978
979  return TCL_OK;
980}
981
982static void
983DeleteSection(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display)
984{
985  SectionItem *sectPtr = (SectionItem *) itemPtr;
986
987  if ((sectPtr->id) &&
988      (Snack_GetSound(sectPtr->interp, sectPtr->soundName) != NULL)) {
989    Snack_RemoveCallback(sectPtr->sound, sectPtr->id);
990  }
991
992  if (sectPtr->soundName != NULL) ckfree(sectPtr->soundName);
993
994  if (sectPtr->coords != NULL) ckfree((char *) sectPtr->coords);
995
996  if (sectPtr->si.hamwin != NULL) ckfree((char *)sectPtr->si.hamwin);
997
998  if (sectPtr->xfft != NULL) ckfree((char *)sectPtr->xfft);
999
1000  if (sectPtr->ffts != NULL) ckfree((char *)sectPtr->ffts);
1001
1002  if (sectPtr->fg != NULL) Tk_FreeColor(sectPtr->fg);
1003
1004  if (sectPtr->fillStipple != None) Tk_FreeBitmap(display, sectPtr->fillStipple);
1005
1006  if (sectPtr->gc != None) Tk_FreeGC(display, sectPtr->gc);
1007
1008  if (sectPtr->sound != NULL) {
1009    if (sectPtr->sound->storeType == SOUND_IN_FILE) {
1010      sectPtr->sound->itemRefCnt--;
1011    }
1012  }
1013}
1014
1015static void
1016ComputeSectionBbox(Tk_Canvas canvas, SectionItem *sectPtr)
1017{
1018  int width = sectPtr->width;
1019  int height = sectPtr->height;
1020  int x = (int) (sectPtr->x + ((sectPtr->x >= 0) ? 0.5 : - 0.5));
1021  int y = (int) (sectPtr->y + ((sectPtr->y >= 0) ? 0.5 : - 0.5));
1022
1023  switch (sectPtr->anchor) {
1024  case TK_ANCHOR_N:
1025    x -= width/2;
1026    break;
1027  case TK_ANCHOR_NE:
1028    x -= width;
1029    break;
1030  case TK_ANCHOR_E:
1031    x -= width;
1032    y -= height/2;
1033    break;
1034  case TK_ANCHOR_SE:
1035    x -= width;
1036    y -= height;
1037    break;
1038  case TK_ANCHOR_S:
1039    x -= width/2;
1040    y -= height;
1041    break;
1042  case TK_ANCHOR_SW:
1043    y -= height;
1044    break;
1045  case TK_ANCHOR_W:
1046    y -= height/2;
1047    break;
1048  case TK_ANCHOR_NW:
1049    break;
1050  case TK_ANCHOR_CENTER:
1051    x -= width/2;
1052    y -= height/2;
1053    break;
1054  }
1055
1056  sectPtr->header.x1 = x;
1057  sectPtr->header.y1 = y;
1058  sectPtr->header.x2 = x + width;
1059  sectPtr->header.y2 = y + height;
1060}
1061
1062static void
1063DisplaySection(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display,
1064	    Drawable drawable, int x, int y, int width, int height)
1065{
1066  SectionItem *sectPtr = (SectionItem *) itemPtr;
1067  double *coords = sectPtr->coords;
1068  int i, nPoints = sectPtr->nPoints;
1069  XPoint *wpts = (XPoint *) ckalloc((unsigned)(nPoints * sizeof(XPoint)));
1070  XPoint *p = wpts;
1071  int xo = sectPtr->header.x1;
1072  int yo = sectPtr->header.y1;
1073
1074  if (sectPtr->debug) Snack_WriteLogInt("Enter DisplaySection", nPoints);
1075
1076  if (sectPtr->gc == None) return;
1077
1078  if (sectPtr->fillStipple != None)
1079    Tk_CanvasSetStippleOrigin(canvas, sectPtr->gc);
1080
1081  for (i = 0; i < sectPtr->nPoints; i++) {
1082    Tk_CanvasDrawableCoords(canvas, xo + coords[0], yo + coords[1],
1083			    &p->x, &p->y);
1084    coords += 2;
1085    p++;
1086  }
1087
1088  XDrawLines(display, drawable, sectPtr->gc, wpts, nPoints,
1089	     CoordModeOrigin);
1090
1091  if (sectPtr->frame) {
1092    Tk_CanvasDrawableCoords(canvas, (double) xo, (double) yo,
1093			    &sectPtr->fpts[0].x, &sectPtr->fpts[0].y);
1094    Tk_CanvasDrawableCoords(canvas, (double) (xo + sectPtr->width - 1),
1095			    (double) yo,
1096			    &sectPtr->fpts[1].x, &sectPtr->fpts[1].y);
1097    Tk_CanvasDrawableCoords(canvas, (double) (xo + sectPtr->width - 1),
1098			    (double) (yo + sectPtr->height - 1),
1099			    &sectPtr->fpts[2].x, &sectPtr->fpts[2].y);
1100    Tk_CanvasDrawableCoords(canvas, (double) xo,
1101			    (double) (yo + sectPtr->height - 1),
1102			    &sectPtr->fpts[3].x, &sectPtr->fpts[3].y);
1103    Tk_CanvasDrawableCoords(canvas, (double) xo, (double) yo,
1104			    &sectPtr->fpts[4].x, &sectPtr->fpts[4].y);
1105    XDrawLines(display, drawable, sectPtr->gc, sectPtr->fpts, 5, CoordModeOrigin);
1106  }
1107
1108  ckfree((char *) wpts);
1109
1110  if (sectPtr->debug) Snack_WriteLog("Exit DisplaySection\n");
1111}
1112
1113static double
1114SectionToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *coords)
1115{
1116  SectionItem *sectPtr = (SectionItem *) itemPtr;
1117  double dx = 0.0, dy = 0.0;
1118  double x1 = sectPtr->header.x1;
1119  double y1 = sectPtr->header.y1;
1120  double x2 = sectPtr->header.x2;
1121  double y2 = sectPtr->header.y2;
1122
1123  if (coords[0] < x1)
1124    dx = x1 - coords[0];
1125  else if (coords[0] > x2)
1126    dx = coords[0] - x2;
1127  else
1128    dx = 0;
1129
1130  if (coords[1] < y1)
1131    dy = y1 - coords[1];
1132  else if (coords[1] > y2)
1133    dy = coords[1] - y2;
1134  else
1135    dy = 0;
1136
1137  return hypot(dx, dy);
1138}
1139
1140static int
1141SectionToArea(Tk_Canvas canvas, Tk_Item *itemPtr, double *rectPtr)
1142{
1143  SectionItem *sectPtr = (SectionItem *) itemPtr;
1144
1145  if ((rectPtr[2] <= sectPtr->header.x1) ||
1146      (rectPtr[0] >= sectPtr->header.x2) ||
1147      (rectPtr[3] <= sectPtr->header.y1) ||
1148      (rectPtr[1] >= sectPtr->header.y2))
1149    return -1;
1150
1151  if ((rectPtr[0] <= sectPtr->header.x1) &&
1152      (rectPtr[1] <= sectPtr->header.y1) &&
1153      (rectPtr[2] >= sectPtr->header.x2) &&
1154      (rectPtr[3] >= sectPtr->header.y2))
1155    return 1;
1156
1157  return 0;
1158}
1159
1160static void
1161ScaleSection(Tk_Canvas canvas, Tk_Item *itemPtr, double ox, double oy,
1162	  double sx, double sy)
1163{
1164  SectionItem *sectPtr = (SectionItem *) itemPtr;
1165  double *coords = sectPtr->coords;
1166  int i;
1167
1168  for (i = 0; i < sectPtr->nPoints; i++) {
1169    coords[0] = ox + sx * (coords[0] - ox);
1170    coords[1] = oy + sy * (coords[1] - oy);
1171    coords += 2;
1172  }
1173  sectPtr->width  = (int) (sx * sectPtr->width);
1174  sectPtr->height = (int) (sy * sectPtr->height);
1175
1176  ComputeSectionBbox(canvas, sectPtr);
1177}
1178
1179static void
1180TranslateSection(Tk_Canvas canvas, Tk_Item *itemPtr, double dx, double dy)
1181{
1182  SectionItem *sectPtr = (SectionItem *) itemPtr;
1183
1184  sectPtr->x += dx;
1185  sectPtr->y += dy;
1186  ComputeSectionBbox(canvas, sectPtr);
1187}
1188
1189static int
1190SectionToPS(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass)
1191{
1192  SectionItem *sectPtr = (SectionItem *) itemPtr;
1193  double  *coords = sectPtr->coords;
1194  int     nPoints = sectPtr->nPoints;
1195  char buffer[100];
1196  int xo = sectPtr->header.x1;
1197  int yo = sectPtr->header.y1;
1198
1199  if (sectPtr->fg == NULL) {
1200    return TCL_OK;
1201  }
1202
1203  Tcl_AppendResult(interp, "%% SECT BEGIN\n", (char *) NULL);
1204
1205  sprintf(buffer, "%.15g %.15g moveto\n", coords[0] + xo,
1206	  Tk_CanvasPsY(canvas, (double) (coords[1] + yo)));
1207  Tcl_AppendResult(interp, buffer, (char *) NULL);
1208  coords += 2;
1209  for (nPoints--; nPoints > 0; nPoints--) {
1210    sprintf(buffer, "%.15g %.15g lineto\n", coords[0] + xo,
1211	    Tk_CanvasPsY(canvas, (double) (coords[1] + yo)));
1212    Tcl_AppendResult(interp, buffer, (char *) NULL);
1213    coords += 2;
1214  }
1215
1216  if (sectPtr->frame) {
1217    sprintf(buffer, "%.15g %.15g moveto\n", (double) xo, Tk_CanvasPsY(canvas, (double) yo));
1218    Tcl_AppendResult(interp, buffer, (char *) NULL);
1219
1220    sprintf(buffer, "%.15g %.15g lineto\n", (double) xo + sectPtr->width - 1,
1221	    Tk_CanvasPsY(canvas, (double) yo));
1222    Tcl_AppendResult(interp, buffer, (char *) NULL);
1223
1224    sprintf(buffer, "%.15g %.15g lineto\n", (double) xo + sectPtr->width - 1,
1225	    Tk_CanvasPsY(canvas, (double) (yo + sectPtr->height - 1)));
1226    Tcl_AppendResult(interp, buffer, (char *) NULL);
1227
1228    sprintf(buffer, "%.15g %.15g lineto\n", (double) xo,
1229	    Tk_CanvasPsY(canvas, (double) (yo + sectPtr->height - 1)));
1230    Tcl_AppendResult(interp, buffer, (char *) NULL);
1231
1232    sprintf(buffer, "%.15g %.15g lineto\n", (double) xo,
1233	    Tk_CanvasPsY(canvas, (double) yo));
1234    Tcl_AppendResult(interp, buffer, (char *) NULL);
1235  }
1236
1237  Tcl_AppendResult(interp, "1 setlinewidth\n", (char *) NULL);
1238  Tcl_AppendResult(interp, "0 setlinecap\n0 setlinejoin\n", (char *) NULL);
1239  if (Tk_CanvasPsColor(interp, canvas, sectPtr->fg) != TCL_OK) {
1240    return TCL_ERROR;
1241  };
1242  if (sectPtr->fillStipple != None) {
1243    Tcl_AppendResult(interp, "StrokeClip ", (char *) NULL);
1244    if (Tk_CanvasPsStipple(interp, canvas, sectPtr->fillStipple) != TCL_OK) {
1245      return TCL_ERROR;
1246    }
1247  } else {
1248    Tcl_AppendResult(interp, "stroke\n", (char *) NULL);
1249  }
1250
1251  Tcl_AppendResult(interp, "%% SECT END\n", (char *) NULL);
1252
1253  return TCL_OK;
1254}
1255