/*-
 * Copyright (c) 1993 Michael B. Durian.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Michael B. Durian.
 * 4. The name of the the Author may be used to endorse or promote 
 *    products derived from this software without specific prior written 
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*
 * tkmGrid.c,v 1.8 1993/05/07 17:51:22 durian Exp
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "tk.h"
#include "tkm.h"

static char cvsid[] = "tkmGrid.c,v 1.8 1993/05/07 17:51:22 durian Exp";


/* the volumes of each quanta for a voice */
typedef struct {
	short *volume;
} Voice;

/* All the data a poor grid widget could ever want */
typedef struct {
	Tk_Window tkwin;
	Display *display;
	Tcl_Interp *interp;

	/* information about the grid */
	char **labels;
	char *labelStr;
	char *pitchStr;
	int *pitches;
	int numLabels;
	int levels;
	int oldLevels; /* so we know how many to free on reconfig */
	int beats;
	int measures;
	int quantization;

	/* information on how to display the grid */
	Tk_3DBorder normalBorder;
	int borderWidth;
	int relief;
	XFontStruct *font;
	XColor *lineColor;
	XColor *measureColor;
	XColor *beatColor;
	XColor *fgColor;
	Tk_3DBorder *levelsBorder;
	GC textGC;
	GC lineGC;
	GC measureGC;
	GC beatGC;
	GC *vertLineColors;
	XSegment *vertLinePos;
	GC *horizLineColors;
	XSegment *horizLinePos;
	int boxWid;
	int boxHt;
	int totalBoxWid;
	int totalBoxHt;
	int lineThickness;
	int labelWidth;
	int padX;
	Voice *voices;
	int numHits;
	int *leftBearings;
	int *rightBearings;
	int width;
	int height;
	int resizedWidth;
	int resizedHeight;

	/* for scrolling */
	int leftHit;
	int rightHit;
	int topLabel;
	int bottomLabel;
	char *xScrollCmd;
	char *yScrollCmd;

	/* misc */
	Cursor cursor;
	int flags;
} DrumGrid;

static char *className = "DrumGrid";

/* possible values for the flags variable */
#define REDRAW_PENDING (1 << 0)
#define UPDATE_XSCROLL (1 << 1)
#define UPDATE_YSCROLL (1 << 2)

/* how to parse configuration specs */
static Tk_ConfigSpec configSpecs[] = {
	{TK_CONFIG_COLOR, "-foreground",  "foreground", "Background",
	    DEF_GRID_FG_COLOR, Tk_Offset(DrumGrid, fgColor),
	    TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_COLOR, "-foreground",  "foreground", "Background",
	    DEF_GRID_FG_MONO, Tk_Offset(DrumGrid, fgColor),
	    TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL,
	    (char *)NULL, 0, 0},

	{TK_CONFIG_BORDER, "-background",  "background", "Background",
	    DEF_GRID_NORMAL_BG_COLOR, Tk_Offset(DrumGrid, normalBorder),
	    TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_BORDER, "-background",  "background", "Background",
	    DEF_GRID_NORMAL_BG_MONO, Tk_Offset(DrumGrid, normalBorder),
	    TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL,
	    (char *)NULL, 0, 0},

	{TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	    DEF_GRID_BORDER_WIDTH, Tk_Offset(DrumGrid, borderWidth), 0},
	{TK_CONFIG_SYNONYM, "-bw", "borderWidth", (char *)NULL,
	    (char *)NULL, 0, 0},

	{TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	    DEF_GRID_CURSOR, Tk_Offset(DrumGrid, cursor), TK_CONFIG_NULL_OK},

	{TK_CONFIG_FONT, "-font", "font", "Font", DEF_GRID_FONT,
	    Tk_Offset(DrumGrid, font), 0},

	{TK_CONFIG_INT, "-height", "height", "Height", DEF_GRID_HEIGHT,
	    Tk_Offset(DrumGrid, height), 0},

	{TK_CONFIG_INT, "-width", "width", "Width", DEF_GRID_WIDTH,
	    Tk_Offset(DrumGrid, width), 0},

	{TK_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_GRID_RELIEF,
	    Tk_Offset(DrumGrid, relief), 0},

	{TK_CONFIG_STRING, "-labels", "labels", "Labels", DEF_GRID_LABELS,
	    Tk_Offset(DrumGrid, labelStr), TK_CONFIG_NULL_OK},

	{TK_CONFIG_STRING, "-pitches", "pitches", "Pitches", DEF_GRID_PITCHES,
	    Tk_Offset(DrumGrid, pitchStr), TK_CONFIG_NULL_OK},

	{TK_CONFIG_INT, "-levels", "levels", "Levels", DEF_GRID_LEVELS,
	    Tk_Offset(DrumGrid, levels), 0},

	{TK_CONFIG_INT, "-beats", "beats", "Beats", DEF_GRID_BEATS,
	    Tk_Offset(DrumGrid, beats), 0},

	{TK_CONFIG_INT, "-measures", "measures", "Measures", DEF_GRID_MEASURES,
	    Tk_Offset(DrumGrid, measures), 0},

	{TK_CONFIG_INT, "-quantization", "quantization", "Quantization",
	    DEF_GRID_QUANTIZATION, Tk_Offset(DrumGrid, quantization), 0},
	{TK_CONFIG_SYNONYM, "-quant", "quantization", (char *)NULL,
	    (char *)NULL, 0, 0},

	{TK_CONFIG_INT, "-linethickness", "lineThickness", "LineThickness",
	    DEF_GRID_LINETHICKNESS, Tk_Offset(DrumGrid, lineThickness), 0},
	{TK_CONFIG_SYNONYM, "-lt", "lineThickness", (char *)NULL,
	    (char *)NULL, 0, 0},

	{TK_CONFIG_INT, "-padx", "padX", "PadX", DEF_GRID_PADX,
	    Tk_Offset(DrumGrid, padX), 0},

	{TK_CONFIG_INT, "-boxwidth", "boxWidth", "BoxWidth", DEF_GRID_BOXWIDTH,
	    Tk_Offset(DrumGrid, boxWid)},
	{TK_CONFIG_SYNONYM, "-bw", "boxWidth", (char *)NULL, (char *)NULL, 0,
	    0},

	{TK_CONFIG_INT, "-boxheight", "boxHeight", "BoxHeight",
	    DEF_GRID_BOXHEIGHT, Tk_Offset(DrumGrid, boxHt)},
	{TK_CONFIG_SYNONYM, "-bh", "boxHeight", (char *)NULL, (char *)NULL, 0,
	    0},

	{TK_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor",
	    DEF_GRID_LINE_COLOR, Tk_Offset(DrumGrid, lineColor)},
	{TK_CONFIG_SYNONYM, "-lc", "lineColor", (char *)NULL, (char *)NULL,
	    0, 0},

	{TK_CONFIG_COLOR, "-measurecolor", "measureColor", "MeasureColor",
	    DEF_GRID_MEASURE_COLOR, Tk_Offset(DrumGrid, measureColor)},
	{TK_CONFIG_SYNONYM, "-mc", "measureColor", (char *)NULL, (char *)NULL,
	    0, 0},

	{TK_CONFIG_COLOR, "-beatcolor", "beatColor", "BeatColor",
	    DEF_GRID_BEAT_COLOR, Tk_Offset(DrumGrid, beatColor)},
	{TK_CONFIG_SYNONYM, "-bc", "beatColor", (char *)NULL, (char *)NULL,
	    0, 0},

	{TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand",
	    "ScrollCommand", DEF_GRID_SCROLL_COMMAND, Tk_Offset(DrumGrid,
	    xScrollCmd)},
	{TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand",
	    "ScrollCommand", DEF_GRID_SCROLL_COMMAND, Tk_Offset(DrumGrid,
	    yScrollCmd)},

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

static char *optString = "configure, down, label add, label remove, \
label list, pitch get, pitch set, pitch list, up, volume get, volume set, \
xview, xnearest, yview, ynearest";

static int AddLabel _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static int AllocateGridLines _ANSI_ARGS_((DrumGrid *));
static void CalculateGridLines _ANSI_ARGS_((DrumGrid *));
static void ChangeDrumGridLeft _ANSI_ARGS_((DrumGrid *, int));
static void ChangeDrumGridTop _ANSI_ARGS_((DrumGrid *, int));
static void ComputeDrumGridGeometry _ANSI_ARGS_((DrumGrid *));
static int ConfigureDrumGrid _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int,
    char **, int));
static void DecreaseVolume _ANSI_ARGS_((DrumGrid *, int, int));
static void DestroyDrumGrid  _ANSI_ARGS_((ClientData));
static void DisplayDrumGrid _ANSI_ARGS_((ClientData));
static void DrawHit _ANSI_ARGS_((DrumGrid *, Pixmap, int, int, int));
static void DrawLabels _ANSI_ARGS_((DrumGrid *, Pixmap));
static void DrumGridEventProc _ANSI_ARGS_((ClientData, XEvent *));
static int DrumGridWidgetCmd _ANSI_ARGS_((ClientData, Tcl_Interp *, int,
    char **));
static void DrumGridUpdateXScroll _ANSI_ARGS_((DrumGrid *));
static void DrumGridUpdateYScroll _ANSI_ARGS_((DrumGrid *));
static void FitSize _ANSI_ARGS_((DrumGrid *));
static int GetLabels _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static int GetPitch _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static int GetVolume _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static int HitRelief _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **, int));
static void IncreaseVolume _ANSI_ARGS_((DrumGrid *, int, int));
static int ListPitches _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static void MakeLabelStr _ANSI_ARGS_((DrumGrid *));
static void MakePitchStr _ANSI_ARGS_((DrumGrid *));
static int RemoveLabel _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static int SetPitch _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static int SetVolume _ANSI_ARGS_((Tcl_Interp *, DrumGrid *, int, char **));
static int XToGrid _ANSI_ARGS_((DrumGrid *, int));
static int YToGrid _ANSI_ARGS_((DrumGrid *, int));

#ifdef TCL_MEM_DEBUG
static char *Tclm_DbCkstrdup _ANSI_ARGS_((char *str));
#define ckstrdup(str) Tclm_DbCkstrdup(str)
#else
#define ckstrdup(str) strdup(str)
#endif

int
Tk_DrumGridCmd(clientData, interp, argc, argv)
	ClientData clientData;
	Tcl_Interp *interp;
	int argc;
	char **argv;
{
	DrumGrid *dgPtr;
	Tk_Window tkwin = (Tk_Window) clientData;
	Tk_Window new;

	if (argc < 2) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " pathName ?options?\"", (char *)NULL);
		return (TCL_ERROR);
	}

	if ((new = Tk_CreateWindowFromPath(interp, tkwin, argv[1],
	    (char *)NULL)) == NULL)
		return (TCL_ERROR);

	if ((dgPtr = (DrumGrid *)ckalloc(sizeof(DrumGrid))) == NULL) {
		Tcl_AppendResult(interp, "Out of memory for DrumGrid",
		    (char *)NULL);
		return (TCL_ERROR);
	}
	
	dgPtr->tkwin = new;
	dgPtr->display = Tk_Display(new);
	dgPtr->interp = interp;
	dgPtr->labels = NULL;
	dgPtr->labelStr = NULL;
	dgPtr->pitchStr = NULL;
	dgPtr->pitches = NULL;
	dgPtr->numLabels = 0;
	dgPtr->levels = 0;
	dgPtr->oldLevels = 0;
	dgPtr->beats = 0;
	dgPtr->measures = 0;
	dgPtr->quantization = 0;
	dgPtr->normalBorder = NULL;
	dgPtr->borderWidth = 0;
	dgPtr->relief = TK_RELIEF_FLAT;
	dgPtr->font = NULL;
	dgPtr->lineColor = NULL;
	dgPtr->measureColor = NULL;
	dgPtr->beatColor = NULL;
	dgPtr->levelsBorder = NULL;
	dgPtr->fgColor = NULL;
	dgPtr->textGC = None;
	dgPtr->lineGC = None;
	dgPtr->measureGC = None;
	dgPtr->beatGC = None;
	dgPtr->levelsBorder = NULL;
	dgPtr->vertLineColors = NULL;
	dgPtr->vertLinePos = NULL;
	dgPtr->horizLineColors = NULL;
	dgPtr->horizLinePos = NULL;
	dgPtr->boxWid = 0;
	dgPtr->boxHt = 0;
	dgPtr->totalBoxWid = 0;
	dgPtr->totalBoxHt = 0;
	dgPtr->lineThickness = 0;
	dgPtr->labelWidth = 0;
	dgPtr->padX = 0;
	dgPtr->voices = NULL;
	dgPtr->numHits = 0;
	dgPtr->leftBearings = NULL;
	dgPtr->rightBearings = NULL;
	dgPtr->width = 0;
	dgPtr->height = 0;
	dgPtr->resizedWidth = -1;
	dgPtr->resizedHeight = -1;
	dgPtr->leftHit = 0;
	dgPtr->rightHit = 0;
	dgPtr->topLabel = 0;
	dgPtr->bottomLabel = 0;
	dgPtr->xScrollCmd = NULL;
	dgPtr->yScrollCmd = NULL;
	dgPtr->cursor = None;
	dgPtr->flags = 0;

	Tk_SetClass(new, className);

	Tk_CreateEventHandler(dgPtr->tkwin, ExposureMask | StructureNotifyMask,
	    DrumGridEventProc, (ClientData)dgPtr);

	Tcl_CreateCommand(interp, Tk_PathName(dgPtr->tkwin),
	    DrumGridWidgetCmd, (ClientData)dgPtr, (void (*)())NULL);

	if (ConfigureDrumGrid(interp, dgPtr, argc - 2, argv + 2, 0) !=
	    TCL_OK) {
		Tk_DestroyWindow(dgPtr->tkwin);
		return (TCL_ERROR);
	}

	interp->result = Tk_PathName(dgPtr->tkwin);
	return (TCL_OK);
}

static int
ConfigureDrumGrid(interp, dgPtr, argc, argv, flags)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
	int flags;
{
	XGCValues gcValues;
	GC newGC;
	long background;
	char **labels;
	char **pitches;
	char *chkPtr;
	char colorStr[20];
	int colorVal;
	int i;
	int j;
	int offset;
	int numPitches;

	if (Tk_ConfigureWidget(interp, dgPtr->tkwin, configSpecs,
	    argc, argv, (char *)dgPtr, flags) != TCL_OK)
		return (TCL_ERROR);

	/* free anything that might already be here */
	if (dgPtr->labels != NULL) {
		for (i = 0; i < dgPtr->numLabels; i++)
			if (dgPtr->labels[i] != NULL)
				ckfree((char *)dgPtr->labels[i]);
		ckfree((char *)dgPtr->labels);
	}

	/* free existing voice data */
	if (dgPtr->voices != NULL) {
		for (i = 0; i < dgPtr->numLabels; i++)
			if (dgPtr->voices[i].volume != NULL)
				ckfree(dgPtr->voices[i].volume);
		ckfree(dgPtr->voices);
		dgPtr->voices = NULL;
	}

	/* parse out labels string */
	if (Tcl_SplitList(interp, dgPtr->labelStr, &dgPtr->numLabels,
	    &labels) != TCL_OK)
		return (TCL_ERROR);

	/* do our own alloction of labels so we can change them easier */
	if ((dgPtr->labels = (char **)ckalloc(sizeof(char *) *
	    dgPtr->numLabels)) == NULL) {
		Tcl_AppendResult(interp, "Not enough memory for labels",
		    (char *)NULL);
		ckfree((char *)labels);
		return (TCL_ERROR);
	}
	for (i = 0; i < dgPtr->numLabels; i++) {
		if ((dgPtr->labels[i] = ckstrdup(labels[i])) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory to ",
			   "copy a label", (char *)NULL);
			ckfree((char *)labels);
			return (TCL_ERROR);
		}
	}
	ckfree((char *)labels);

	/* parse out the pitches */
	if (dgPtr->pitchStr == NULL)
		numPitches = 0;
	else {
		if (Tcl_SplitList(interp, dgPtr->pitchStr, &numPitches,
		    &pitches) != TCL_OK)
			return (TCL_ERROR);
	}

	if (dgPtr->pitches != NULL)
		ckfree(dgPtr->pitches);

	if ((dgPtr->pitches = (int *)ckalloc(sizeof(int) * dgPtr->numLabels))
	    == NULL) {
		Tcl_AppendResult(interp, "Not enough memory for pitch ",
		    "values", (char *)NULL);
		ckfree((char *)pitches);
		return (TCL_ERROR);
	}
	for (i = 0; i < dgPtr->numLabels; i++) {
		if (i >= numPitches)
			dgPtr->pitches[i] = -1;
		else {
			dgPtr->pitches[i] = (int)strtol(pitches[i], &chkPtr,
			    0);
			if (chkPtr == pitches[i]) {
				Tcl_AppendResult(interp, "Bad pitch value ",
				    pitches[i], (char *)NULL);
				ckfree((char *)pitches);
				return (TCL_ERROR);
			}
		}
	}
	ckfree((char *)pitches);

	Tk_SetBackgroundFromBorder(dgPtr->tkwin, dgPtr->normalBorder);

	background = Tk_3DBorderColor(dgPtr->normalBorder)->pixel;

	/* set up text GC */
	gcValues.font = dgPtr->font->fid;
	gcValues.foreground = dgPtr->fgColor->pixel;
	gcValues.background = background;
	gcValues.graphics_exposures = False;
	newGC = Tk_GetGC(dgPtr->tkwin, GCForeground | GCBackground | GCFont
	    | GCGraphicsExposures, &gcValues);
	if (dgPtr->textGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->textGC);
	dgPtr->textGC = newGC;

	/* set up normal line GC */
	gcValues.function = GXcopy;
	gcValues.foreground = dgPtr->lineColor->pixel;
	gcValues.background = background;
	gcValues.line_style = LineSolid;
	gcValues.line_width = dgPtr->lineThickness;
	gcValues.graphics_exposures = False;
	newGC = Tk_GetGC(dgPtr->tkwin, GCFunction | GCForeground |
	    GCBackground | GCLineStyle | GCLineWidth | GCGraphicsExposures,
	    &gcValues);
	if (dgPtr->lineGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->lineGC);
	dgPtr->lineGC = newGC;

	/* set up measure line GC */
	gcValues.function = GXcopy;
	gcValues.foreground = dgPtr->measureColor->pixel;
	gcValues.background = background;
	gcValues.line_style = LineSolid;
	gcValues.line_width = dgPtr->lineThickness;
	gcValues.graphics_exposures = False;
	newGC = Tk_GetGC(dgPtr->tkwin, GCFunction | GCForeground |
	    GCBackground | GCLineStyle | GCLineWidth | GCGraphicsExposures,
	    &gcValues);
	if (dgPtr->measureGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->measureGC);
	dgPtr->measureGC = newGC;

	/* set up beat line GC */
	gcValues.function = GXcopy;
	gcValues.foreground = dgPtr->beatColor->pixel;
	gcValues.background = background;
	gcValues.line_style = LineSolid;
	gcValues.line_width = dgPtr->lineThickness;
	gcValues.graphics_exposures = False;
	newGC = Tk_GetGC(dgPtr->tkwin, GCFunction | GCForeground |
	    GCBackground | GCLineStyle | GCLineWidth | GCGraphicsExposures,
	    &gcValues);
	if (dgPtr->beatGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->beatGC);
	dgPtr->beatGC = newGC;

	if (dgPtr->levelsBorder != NULL) {
		for (i = 0; i < dgPtr->oldLevels; i++)
			if (dgPtr->levelsBorder[i] != None)
				Tk_Free3DBorder(dgPtr->levelsBorder[i]);
		ckfree((char *)dgPtr->levelsBorder);
	}

	if ((dgPtr->levelsBorder = (Tk_3DBorder *)ckalloc(sizeof(Tk_3DBorder)
	    * dgPtr->levels)) == NULL) {
		Tcl_AppendResult(interp, "Not enough memory for level ",
		    "Borders's", (char *)NULL);
		return (TCL_ERROR);
	}

#define LEVEL_MIN 10000
	for (i = 0; i < dgPtr->levels; i++) {
		colorVal = ((0xffff - LEVEL_MIN) / dgPtr->levels) * i +
		    LEVEL_MIN;
		sprintf(colorStr, "#%04x%04x%04x", colorVal, colorVal,
		    colorVal);

		if ((dgPtr->levelsBorder[i] = Tk_Get3DBorder(interp,
		    dgPtr->tkwin, None, Tk_GetUid(colorStr))) == NULL) {
			Tcl_AppendResult(interp, "Couldn't level Border",
			    (char *)NULL);
			return (TCL_ERROR);
		}
	}

	dgPtr->oldLevels = dgPtr->levels;

	/* fix quantization to be a multiple of 4 */
	if ((offset = dgPtr->quantization % 4) < 2)
		dgPtr->quantization -= offset;
	else
		dgPtr->quantization += 4 - offset;

	dgPtr->numHits = dgPtr->beats * dgPtr->measures * dgPtr->quantization
	    / 4;

	/* make the voices */
	if (dgPtr->numLabels != 0)
	if ((dgPtr->voices = (Voice *)ckalloc(sizeof(Voice) * dgPtr->numLabels))
	    == NULL) {
		Tcl_AppendResult(interp, "Not enough memory for voices",
		    (char *)NULL);
		return (TCL_ERROR);
	}

	/* make hits(quanta) inside voices */
	for (i = 0; i < dgPtr->numLabels; i++) {
		if ((dgPtr->voices[i].volume = (short *)ckalloc(dgPtr->numHits
		     * sizeof(short))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory for ",
			   "hits in voice", (char *)NULL);
			return (TCL_ERROR);
		}
		for (j = 0; j < dgPtr->numHits; j++)
			dgPtr->voices[i].volume[j] = 0;
	}

	/* decide how the scroll region should look */
	dgPtr->leftHit = 0;
	dgPtr->topLabel = 0;
	if (dgPtr->resizedWidth != -1 && dgPtr->resizedHeight != -1)
		FitSize(dgPtr);
	else {
		if (dgPtr->width == 0)
			/* by default do one measure */
			dgPtr->rightHit = dgPtr->beats *
			    dgPtr->quantization / 4 - 1;
		else
			dgPtr->rightHit = dgPtr->width - 1;
		if (dgPtr->height == 0)
			/* by default do them all */
			dgPtr->bottomLabel = dgPtr->numLabels - 1;
		else
			dgPtr->bottomLabel = dgPtr->height - 1;
	}

	dgPtr->flags |= UPDATE_YSCROLL | UPDATE_XSCROLL;

	/* make room for grid lines */
	if (AllocateGridLines(dgPtr) != TCL_OK)
		return (TCL_ERROR);

	/* make room for bearings */
	if (dgPtr->leftBearings != NULL)
		ckfree(dgPtr->leftBearings);
	if ((dgPtr->leftBearings = (int *)ckalloc(sizeof(int) *
	    dgPtr->numLabels)) == NULL) {
		Tcl_AppendResult(interp, "Not enough memory for leftbearings",
		    (char *)NULL);
		return (TCL_ERROR);
	}

	if (dgPtr->rightBearings != NULL)
		ckfree(dgPtr->rightBearings);
	if ((dgPtr->rightBearings = (int *)ckalloc(sizeof(int) *
	    dgPtr->numLabels)) == NULL) {
		Tcl_AppendResult(interp, "Not enough memory for rightbearings",
		    (char *)NULL);
		return (TCL_ERROR);
	}

	/* calculate geometry */
	ComputeDrumGridGeometry(dgPtr);

	/* display it */
	if (Tk_IsMapped(dgPtr->tkwin) && (!dgPtr->flags & REDRAW_PENDING)) {
		Tk_DoWhenIdle(DisplayDrumGrid, (ClientData)dgPtr);
		dgPtr->flags |= REDRAW_PENDING;
	}

	return (TCL_OK);
}

static void
DestroyDrumGrid (clientData)
	ClientData clientData;
{
	DrumGrid *dgPtr = (DrumGrid *)clientData;
	int i;

	if (dgPtr->labels != NULL) {
		for (i = 0; i < dgPtr->numLabels; i++)
			if (dgPtr->labels[i] != NULL)
				ckfree((char *)dgPtr->labels[i]);
		ckfree((char *)dgPtr->labels);
	}
	if (dgPtr->labelStr != NULL)
		ckfree(dgPtr->labelStr);
	if (dgPtr->pitches != NULL)
		ckfree((char *)dgPtr->pitches);
	if (dgPtr->pitchStr != NULL)
		ckfree(dgPtr->pitchStr);
	if (dgPtr->normalBorder != NULL)
		Tk_Free3DBorder(dgPtr->normalBorder);
	if (dgPtr->font != NULL)
		Tk_FreeFontStruct(dgPtr->font);
	if (dgPtr->lineColor != NULL)
		Tk_FreeColor(dgPtr->lineColor);
	if (dgPtr->measureColor != NULL)
		Tk_FreeColor(dgPtr->measureColor);
	if (dgPtr->beatColor != NULL)
		Tk_FreeColor(dgPtr->beatColor);
	if (dgPtr->fgColor != NULL)
		Tk_FreeColor(dgPtr->fgColor);
	if (dgPtr->textGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->textGC);
	if (dgPtr->lineGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->lineGC);
	if (dgPtr->measureGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->measureGC);
	if (dgPtr->beatGC != None)
		Tk_FreeGC(dgPtr->display, dgPtr->beatGC);
	if (dgPtr->levelsBorder != NULL) {
		for (i = 0; i < dgPtr->oldLevels; i++)
			if (dgPtr->levelsBorder[i] != None)
				Tk_Free3DBorder(dgPtr->levelsBorder[i]);
		ckfree((char *)dgPtr->levelsBorder);
	}
	if (dgPtr->vertLineColors != NULL)
		ckfree((char *)dgPtr->vertLineColors);
	if (dgPtr->vertLinePos != NULL)
		ckfree((char *)dgPtr->vertLinePos);
	if (dgPtr->horizLineColors != NULL)
		ckfree((char *)dgPtr->horizLineColors);
	if (dgPtr->horizLinePos != NULL)
		ckfree((char *)dgPtr->horizLinePos);
	if (dgPtr->voices != NULL) {
		for (i = 0; i < dgPtr->numLabels; i++)
			if (dgPtr->voices[i].volume != NULL)
				ckfree((char *)dgPtr->voices[i].volume);
		ckfree((char *)dgPtr->voices);
	}
	if (dgPtr->leftBearings != NULL)
		ckfree((char *)dgPtr->leftBearings);
	if (dgPtr->rightBearings != NULL)
		ckfree((char *)dgPtr->rightBearings);
	if (dgPtr->xScrollCmd != NULL)
		ckfree(dgPtr->xScrollCmd);
	if (dgPtr->yScrollCmd != NULL)
		ckfree(dgPtr->yScrollCmd);
	if (dgPtr->cursor != None)
		Tk_FreeCursor(dgPtr->display, dgPtr->cursor);
}

static void
DisplayDrumGrid(clientData)
	ClientData clientData;
{
	DrumGrid *dgPtr = (DrumGrid *)clientData;
	Tk_Window tkwin = dgPtr->tkwin;
	Pixmap pixmap;
	Tk_3DBorder border;
	int i;
	int j;
	int x;
	int y;

	dgPtr->flags &= ~REDRAW_PENDING;
	if (dgPtr->flags & UPDATE_XSCROLL)
		DrumGridUpdateXScroll(dgPtr);
	if (dgPtr->flags & UPDATE_YSCROLL)
		DrumGridUpdateYScroll(dgPtr);
	dgPtr->flags &= ~(UPDATE_XSCROLL | UPDATE_YSCROLL);
	if (tkwin == NULL || !Tk_IsMapped(tkwin))
		return;

	border = dgPtr->normalBorder;
	pixmap = XCreatePixmap(dgPtr->display, Tk_WindowId(tkwin),
	    Tk_Width(tkwin), Tk_Height(tkwin),
	    DefaultDepthOfScreen(Tk_Screen(tkwin)));
	Tk_Fill3DRectangle(dgPtr->display, pixmap, border, 0, 0,
	    Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);

	/* horizontal lines */
	for (i = dgPtr->topLabel, j = 0; i <= dgPtr->bottomLabel + 1; i++, j++)
		XDrawSegments(dgPtr->display, pixmap,
		    dgPtr->horizLineColors[i], &dgPtr->horizLinePos[j], 1);

	/* vertical lines */
	/* always do line to left of labels */
	XDrawSegments(dgPtr->display, pixmap,
	    dgPtr->vertLineColors[dgPtr->numHits + 1],
	    &dgPtr->vertLinePos[dgPtr->rightHit - dgPtr->leftHit + 2], 1);

	for (i = dgPtr->leftHit, j = 0; i <= dgPtr->rightHit + 1; i++, j++)
		XDrawSegments(dgPtr->display, pixmap,
		    dgPtr->vertLineColors[i], &dgPtr->vertLinePos[j], 1);

	DrawLabels(dgPtr, pixmap);

	/* draw in hits */
	for (x = dgPtr->leftHit; x < dgPtr->rightHit + 1; x++)
		for (y = dgPtr->topLabel; y < dgPtr->bottomLabel + 1; y++)
			DrawHit(dgPtr, (Pixmap)pixmap, x, y, 0);

	/* draw border */
	if (dgPtr->relief != TK_RELIEF_FLAT)
		Tk_Draw3DRectangle(dgPtr->display, pixmap, border, 0, 0,
		    Tk_Width(tkwin), Tk_Height(tkwin), dgPtr->borderWidth,
		    dgPtr->relief);

	XCopyArea(dgPtr->display, pixmap, Tk_WindowId(tkwin),
	    dgPtr->textGC, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, 0);
	XFreePixmap(dgPtr->display, pixmap);
}

static void
DrawLabels(dgPtr, pixmap)
	DrumGrid *dgPtr;
	Pixmap pixmap;
{
	int i;
	int top;
	int x;
	int y;

	top = dgPtr->borderWidth;
	for (i = dgPtr->topLabel; i < dgPtr->bottomLabel + 1; i++) {
		x = dgPtr->borderWidth + dgPtr->padX + dgPtr->leftBearings[i]
		    + 1;
		y = top + (dgPtr->boxHt + dgPtr->font->ascent -
		    dgPtr->font->descent) / 2;
		if (dgPtr->relief == TK_RELIEF_RAISED) {
			x--;
			y--;
		} else if (dgPtr->relief == TK_RELIEF_SUNKEN) {
			x++;
			y++;
		}
		XDrawString(dgPtr->display, pixmap,
		    dgPtr->textGC, x, y, dgPtr->labels[i],
		    strlen(dgPtr->labels[i]));
		top += dgPtr->boxHt;
	}
}

static void
DrawHit(dgPtr, pixmap, x, y, depressed)
	DrumGrid *dgPtr;
	Pixmap pixmap;
	int x;
	int y;
	int depressed;
{
	int x_pos;
	int y_pos;
	int width;
	int height;
	int relief;
	int volume;

	x_pos = dgPtr->labelWidth + dgPtr->borderWidth + (x - dgPtr->leftHit)
	    * dgPtr->boxWid + dgPtr->lineThickness / 2;
	y_pos = dgPtr->borderWidth + (y - dgPtr->topLabel) * dgPtr->boxHt +
	    dgPtr->lineThickness / 2;

	width = dgPtr->boxWid - dgPtr->lineThickness;
	height = dgPtr->boxHt - dgPtr->lineThickness;
	volume = dgPtr->voices[y].volume[x];
	if (depressed)
		relief = TK_RELIEF_SUNKEN;
	else if (volume == 0)
		relief = TK_RELIEF_FLAT;
	else
		relief = TK_RELIEF_RAISED;
	Tk_Fill3DRectangle(dgPtr->display, pixmap,
	    dgPtr->levelsBorder[volume], x_pos, y_pos, width, height,
	    dgPtr->borderWidth, relief);
}

static void
ComputeDrumGridGeometry(dgPtr)
	DrumGrid *dgPtr;
{
	XCharStruct bbox;
	int foo;
	int i;
	int width;
	int height;
	unsigned int labelWidth;

	labelWidth = 0;
	for (i = 0; i < dgPtr->numLabels; i++) {
		XTextExtents(dgPtr->font, dgPtr->labels[i],
		    strlen(dgPtr->labels[i]), &foo, &foo, &foo, &bbox);
		dgPtr->leftBearings[i] = bbox.lbearing;
		dgPtr->rightBearings[i] = bbox.rbearing;
		labelWidth = labelWidth > bbox.lbearing + bbox.rbearing ?
		    labelWidth : bbox.lbearing + bbox.rbearing;
	}
	labelWidth += 2 * dgPtr->padX + 1;
	dgPtr->labelWidth = labelWidth;

	width = labelWidth + (dgPtr->rightHit - dgPtr->leftHit + 1) *
	    dgPtr->boxWid;
	height = (dgPtr->bottomLabel - dgPtr->topLabel + 1) * dgPtr->boxHt;

	/* set up grid lines */
	CalculateGridLines(dgPtr);

	Tk_GeometryRequest(dgPtr->tkwin, width + dgPtr->borderWidth * 2 + 2,
	    height + dgPtr->borderWidth * 2 + 2);
	Tk_SetInternalBorder(dgPtr->tkwin, dgPtr->borderWidth);
}

static void
CalculateGridLines(dgPtr)
	DrumGrid *dgPtr;
{
	GC *lineColorPtr;
	XSegment *linePosPtr;
	int hitsPerMeasure;
	int i;

	lineColorPtr = dgPtr->vertLineColors;
	linePosPtr = dgPtr->vertLinePos;
	hitsPerMeasure = dgPtr->beats * dgPtr->quantization / 4;

	for (i = 0; i < dgPtr->numHits + 1; i++, lineColorPtr++) {
		/* 1st and last lines are normal */
		if (i == 0 || i == dgPtr->numHits || i % (dgPtr->quantization
		    / 4) != 0)
			*lineColorPtr = dgPtr->lineGC;
		else if (i % hitsPerMeasure == 0)
			*lineColorPtr = dgPtr->measureGC;
		else
			*lineColorPtr = dgPtr->beatGC;
	}
	for (i = 0; i <= dgPtr->rightHit - dgPtr->leftHit + 1; i++,
	    linePosPtr++) {
		linePosPtr->x1 = linePosPtr->x2 = i * dgPtr->boxWid
		    + dgPtr->labelWidth + dgPtr->borderWidth;
		linePosPtr->y1 = dgPtr->borderWidth;
		linePosPtr->y2 = dgPtr->boxHt * (dgPtr->bottomLabel -
		    dgPtr->topLabel + 1) + dgPtr->borderWidth;
	}

	/* line to the left of the labels */
	*lineColorPtr = dgPtr->lineGC;
	linePosPtr->x1 = linePosPtr->x2 = dgPtr->borderWidth;
	linePosPtr->y1 = dgPtr->borderWidth;
	linePosPtr->y2 = dgPtr->borderWidth + dgPtr->boxHt *
	    (dgPtr->bottomLabel - dgPtr->topLabel + 1);

	/* horizontal lines */
	lineColorPtr = dgPtr->horizLineColors;
	linePosPtr = dgPtr->horizLinePos;
	for (i = 0; i < dgPtr->numLabels + 1; i++, lineColorPtr++)
		*lineColorPtr = dgPtr->lineGC;

	for (i = 0; i <= dgPtr->bottomLabel - dgPtr->topLabel + 1; i++,
	    linePosPtr++) {
		linePosPtr->x1 = dgPtr->borderWidth;
		linePosPtr->x2 = dgPtr->boxWid * (dgPtr->rightHit -
		    dgPtr->leftHit + 1) + dgPtr->labelWidth +
		    dgPtr->borderWidth;
		linePosPtr->y1 = linePosPtr->y2 = i * dgPtr->boxHt
		    + dgPtr->borderWidth;
	}
}


static void
DrumGridEventProc(clientData, eventPtr)
	ClientData clientData;
	XEvent *eventPtr;
{
	DrumGrid *dgPtr = (DrumGrid *)clientData;
	int num_boxes;
	int num_labels;

	switch (eventPtr->type) {
	case Expose:
		if (eventPtr->xexpose.count == 0) {
			if ((dgPtr->tkwin != NULL) && !(dgPtr->flags &
			    REDRAW_PENDING)) {
				Tk_DoWhenIdle(DisplayDrumGrid,
				    (ClientData)dgPtr);
				dgPtr->flags |= REDRAW_PENDING;
			}
		}
		break;
	case DestroyNotify:
		Tcl_DeleteCommand(dgPtr->interp, Tk_PathName(dgPtr->tkwin));
		dgPtr->tkwin = NULL;
		if (dgPtr->flags & REDRAW_PENDING)
			Tk_CancelIdleCall(DisplayDrumGrid, (ClientData)dgPtr);
		Tk_EventuallyFree((ClientData)dgPtr, DestroyDrumGrid);
		break;
	case ConfigureNotify:
		num_boxes = (Tk_Width(dgPtr->tkwin) - 2 -
		    2 * dgPtr->borderWidth - dgPtr->labelWidth) /
		    dgPtr->boxWid;
		num_labels = (Tk_Height(dgPtr->tkwin) - 2 -
		    2 * dgPtr->borderWidth) / dgPtr->boxHt;
		dgPtr->resizedWidth = num_boxes;
		dgPtr->resizedHeight = num_labels;
		FitSize(dgPtr);
		if (AllocateGridLines(dgPtr) != TCL_OK)
			Tk_BackgroundError(dgPtr->interp);
		CalculateGridLines(dgPtr);
		dgPtr->flags |= UPDATE_XSCROLL | UPDATE_YSCROLL;
		break;
	}
}

static int
XToGrid(dgPtr, x)
	DrumGrid *dgPtr;
	int x;
{
	int gridX;

	if (x < dgPtr->labelWidth + dgPtr->borderWidth || x > dgPtr->labelWidth
	    + (dgPtr->rightHit - dgPtr->leftHit + 1) * dgPtr->boxWid)
		return (-1);
	gridX = (x - dgPtr->labelWidth - dgPtr->borderWidth) / dgPtr->boxWid;
	gridX += dgPtr->leftHit;
	return (gridX);
}

static int
YToGrid(dgPtr, y)
	DrumGrid *dgPtr;
	int y;
{
	int gridY;

	if (y < dgPtr->borderWidth || y > dgPtr->borderWidth +
	    (dgPtr->bottomLabel - dgPtr->topLabel + 1) * dgPtr->boxHt)
		return (-1);
	gridY = (y - dgPtr->borderWidth) / dgPtr->boxHt;
	gridY += dgPtr->topLabel;
	return (gridY);
}

/*
 * IncreaseVolume and DecreaseVolume are no longer used now that
 * button bindings are done in the script.
static void
IncreaseVolume(dgPtr, x, y)
	DrumGrid *dgPtr;
	int x;
	int y;
{

	dgPtr->voices[y].volume[x]++;
	if (dgPtr->voices[y].volume[x] >= dgPtr->levels)
		dgPtr->voices[y].volume[x] = dgPtr->levels - 1;
	DrawHit(dgPtr, Tk_WindowId(dgPtr->tkwin), x, y, 0);
}

static void
DecreaseVolume(dgPtr, x, y)
	DrumGrid *dgPtr;
	int x;
	int y;
{

	dgPtr->voices[y].volume[x]--;
	if (dgPtr->voices[y].volume[x] < 0)
		dgPtr->voices[y].volume[x] = 0;
	DrawHit(dgPtr, Tk_WindowId(dgPtr->tkwin), x, y, 0);
}
 * end of comment out of IncreaseVolume and DecreaseVolume
 */

static int
DrumGridWidgetCmd(clientData, interp, argc, argv)
	ClientData clientData;
	Tcl_Interp *interp;
	int argc;
	char **argv;
{
	DrumGrid *dgPtr = (DrumGrid *)clientData;
	int index;
	int result;
	int length;
	int pos;
	char c;

	result = TCL_OK;

	if (argc < 2) {
		Tcl_AppendResult(interp, "Wrong # args: should be \"",
		    argv[0], " option ?arg arg ...?\"", (char *)NULL);
		return (TCL_ERROR);
	}
	Tk_Preserve((ClientData)dgPtr);
	c = argv[1][0];
	length = strlen(argv[1]);
	switch (c) {
	case 'c':
		if (strncmp(argv[1], "configure", length) == 0) {
			if (argc == 2)
				result = Tk_ConfigureInfo(interp,
				    dgPtr->tkwin, configSpecs, (char *)dgPtr,
				    (char *)NULL, 0);
			else if (argc == 3)
				result = Tk_ConfigureInfo(interp,
				    dgPtr->tkwin, configSpecs, (char *)dgPtr,
				    argv[2], 0);
			else
				result = ConfigureDrumGrid(interp, dgPtr,
				    argc - 2, argv + 2, TK_CONFIG_ARGV_ONLY);
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	case 'd':
		if (strncmp(argv[1], "down", length) == 0) {
			if (argc == 4)
				result = HitRelief(interp, dgPtr, argc - 2,
				    argv + 2, 1);
			else {
				Tcl_AppendResult(interp, "wrong # args: ",
				    "should be \"", argv[0], " up x y\"",
				    (char *)NULL);
				result = TCL_ERROR;
			}
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	case 'l':
		if (strncmp(argv[1], "label", length) == 0) {
			length = strlen(argv[2]);
			if (strncmp(argv[2], "add", length) == 0)
				result = AddLabel(interp, dgPtr, argc - 3,
				    argv + 3);
			else if (strncmp(argv[2], "remove", length) == 0)
				result = RemoveLabel(interp, dgPtr, argc - 3,
				    argv + 3);
			else if (strncmp(argv[2], "list", length) == 0)
				result = GetLabels(interp, dgPtr, argc - 3,
				    argv + 3);
			else {
				Tcl_AppendResult(interp, "bad option ",
				    argv[2], ": must be add, remove or list",
				    (char *)NULL);
				result = TCL_ERROR;
			}
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	case 'p':
		if (strncmp(argv[1], "pitch", length) == 0) {
			length = strlen(argv[2]);
			if (strncmp(argv[2], "get", length) == 0)
				result = GetPitch(interp, dgPtr, argc - 3,
				    argv + 3);
			else if (strncmp(argv[2], "set", length) == 0)
				result = SetPitch(interp, dgPtr, argc - 3,
				    argv + 3);
			else if (strncmp(argv[2], "list", length) == 0)
				result = ListPitches(interp, dgPtr, argc - 3,
				    argv + 3);
			else {
				Tcl_AppendResult(interp, "bad option ",
				    argv[2], ": must be set, get or list",
				    (char *)NULL);
				result = TCL_ERROR;
			}
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	case 'u':
		if (strncmp(argv[1], "up", length) == 0) {
			if (argc == 4)
				result = HitRelief(interp, dgPtr, argc - 2,
				    argv + 2, 0);
			else {
				Tcl_AppendResult(interp, "wrong # args: ",
				    "should be \"", argv[0], " up x y\"",
				    (char *)NULL);
				result = TCL_ERROR;
			}
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	case 'v':
		if (strncmp(argv[1], "volume", length) == 0) {
			length = strlen(argv[2]);
			if (strncmp(argv[2], "get", length) == 0)
				result = GetVolume(interp, dgPtr, argc - 3,
				    argv + 3);
			else if (strncmp(argv[2], "set", length) == 0)
				result = SetVolume(interp, dgPtr, argc - 3,
				    argv + 3);
			else {
				Tcl_AppendResult(interp, "bad option ",
				    argv[2], ": must be set or get",
				    (char *)NULL);
				result = TCL_ERROR;
			}
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	case 'x':
		if (strncmp(argv[1], "xview", length) == 0) {
			if (argc < 2 || argc > 3) {
				Tcl_AppendResult(interp, "wrong # args: ",
				    "should be \"", argv[0], " xview ?index?\"",
				    (char *)NULL);
				result = TCL_ERROR;
			} else if (argc == 2) {
				sprintf(interp->result, "%d", dgPtr->leftHit);
			} else if (argc == 3) {
				if (Tcl_GetInt(interp, argv[2], &index)
				    != TCL_OK)
					result = TCL_ERROR;
				else
					ChangeDrumGridLeft(dgPtr, index);
			}
		} else if (strncmp(argv[1], "xnearest", length) == 0) {
			if (argc != 3) {
				Tcl_AppendResult(interp, "wrong # args: ",
				    "should be \"", argv[0], " xnearest xpos\"",
				    (char *)NULL);
				result = TCL_ERROR;
			} else {
				if (Tcl_GetInt(interp, argv[2], &pos) !=
				    TCL_OK)
					result = TCL_ERROR;
				else
					sprintf(interp->result, "%d",
					    XToGrid(dgPtr, pos));
			}
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	case 'y':
		if (strncmp(argv[1], "yview", length) == 0) {
			if (argc < 2 || argc > 3) {
				Tcl_AppendResult(interp, "wrong # args: ",
				    "should be \"", argv[0], " yview ?index?\"",
				    (char *)NULL);
				result = TCL_ERROR;
			} else if (argc == 2) {
				sprintf(interp->result, "%d", dgPtr->topLabel);
			} else {
				if (Tcl_GetInt(interp, argv[2], &index)
				    != TCL_OK)
					result = TCL_ERROR;
				else
					ChangeDrumGridTop(dgPtr, index);
			}
		} else if (strncmp(argv[1], "ynearest", length) == 0) {
			if (argc != 3) {
				Tcl_AppendResult(interp, "wrong # args: ",
				    "should be \"", argv[0], " ynearest ypos\"",
				    (char *)NULL);
				result = TCL_ERROR;
			} else {
				if (Tcl_GetInt(interp, argv[2], &pos) !=
				    TCL_OK)
					result = TCL_ERROR;
				else
					sprintf(interp->result, "%d",
					    YToGrid(dgPtr, pos));
			}
		} else {
			Tcl_AppendResult(interp, "bad option ", argv[1],
			    ": must be one of ", optString, (char *)NULL);
			result = TCL_ERROR;
		}
		break;
	default:
		Tcl_AppendResult(interp, "bad option ", argv[1],
		    ": must be one of ", optString, (char *)NULL);
		result = TCL_ERROR;
	}

	Tk_Release((ClientData)dgPtr);
	return (result);
}

static int
GetVolume(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	Tcl_Interp *temp_interp;
	char foo[10];
	int x;
	int y;
	char *chkPtr;

	if (argc < 1 || argc > 2) {
		Tcl_AppendResult(interp, "Must specify 'all' or x and y grid ",
		    "positions", (char *)NULL);
		return (TCL_ERROR);
	}

	if (strcmp(argv[0], "all") == 0) {
		temp_interp = Tcl_CreateInterp();
		for (x = 0; x < dgPtr->numHits; x++) {
			for (y = 0; y < dgPtr->numLabels; y++) {
				sprintf(foo, "%d", dgPtr->voices[y].volume[x]);
				Tcl_AppendElement(temp_interp, foo, 0);
			}
			Tcl_AppendElement(interp, temp_interp->result, 0);
			Tcl_ResetResult(temp_interp);
		}
		Tcl_DeleteInterp(temp_interp);
	} else {
		x = (int)strtol(argv[0], &chkPtr, 0);
		if (argv[0] == chkPtr || x >= dgPtr->numHits || x < 0) {
			Tcl_AppendResult(interp, "Bad x grid position value ",
			    argv[0], (char *)NULL);
			return (TCL_ERROR);
		}

		y = (int)strtol(argv[1], &chkPtr, 0);
		if (argv[1] == chkPtr || y >= dgPtr->numLabels || y < 0) {
			Tcl_AppendResult(interp, "Bad y grid position value ",
			    argv[1], (char *)NULL);
			return (TCL_ERROR);
		}

		sprintf(interp->result, "%d", dgPtr->voices[y].volume[x]);
	}
	return (TCL_OK);
}

static int
SetVolume(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	int x;
	int y;
	int volume;
	char *chkPtr;

	if (argc != 3) {
		Tcl_AppendResult(interp, "Must specify an x and y grid ",
		    "positions and a new volume", (char *)NULL);
		return (TCL_ERROR);
	}

	x = (int)strtol(argv[0], &chkPtr, 0);
	if (argv[0] == chkPtr || x >= dgPtr->numHits || x < 0) {
		Tcl_AppendResult(interp, "Bad x grid position value ",
		    argv[0], (char *)NULL);
		return (TCL_ERROR);
	}

	y = (int)strtol(argv[1], &chkPtr, 0);
	if (argv[1] == chkPtr || y >= dgPtr->numLabels || y < 0) {
		Tcl_AppendResult(interp, "Bad y grid position value ",
		    argv[1], (char *)NULL);
		return (TCL_ERROR);
	}

	volume = (int)strtol(argv[2], &chkPtr, 0);
	if (argv[2] == chkPtr) {
		Tcl_AppendResult(interp, "Bad volume value ",
		    argv[2], (char *)NULL);
		return (TCL_ERROR);
	}

	if (volume < 0)
		volume = 0;
	if (volume >= dgPtr->levels)
		volume = dgPtr->levels - 1;
	dgPtr->voices[y].volume[x] = volume;
	DrawHit(dgPtr, Tk_WindowId(dgPtr->tkwin), x, y, 0);
	sprintf(interp->result, "%d", volume);
	return (TCL_OK);
}

static int
GetPitch(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	int y;
	char *chkPtr;

	if (argc != 1) {
		Tcl_AppendResult(interp, "Must specify a y grid ",
		    "position", (char *)NULL);
		return (TCL_ERROR);
	}

	y = (int)strtol(argv[0], &chkPtr, 0);
	if (argv[0] == chkPtr || y >= dgPtr->numLabels || y < 0) {
		Tcl_AppendResult(interp, "Bad y grid position value ",
		    argv[0], (char *)NULL);
		return (TCL_ERROR);
	}

	sprintf(interp->result, "%d", dgPtr->pitches[y]);
	return (TCL_OK);
}

static int
SetPitch(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	int y;
	int pitch;
	char *chkPtr;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Must specify a y grid ",
		    "positions and a new pitch", (char *)NULL);
		return (TCL_ERROR);
	}

	y = (int)strtol(argv[0], &chkPtr, 0);
	if (argv[0] == chkPtr || y >= dgPtr->numLabels || y < 0) {
		Tcl_AppendResult(interp, "Bad y grid position value ",
		    argv[1], (char *)NULL);
		return (TCL_ERROR);
	}

	pitch = (int)strtol(argv[1], &chkPtr, 0);
	if (argv[1] == chkPtr) {
		Tcl_AppendResult(interp, "Bad pitch value ",
		    argv[1], (char *)NULL);
		return (TCL_ERROR);
	}

	dgPtr->pitches[y] = pitch;
	sprintf(interp->result, "%d", pitch);
	return (TCL_OK);
}

static int
HitRelief(interp, dgPtr, argc, argv, depressed)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
	int depressed;
{
	char *chk_ptr;
	int x;
	int y;

	x = (int)strtol(argv[0], &chk_ptr, 0);
	if (chk_ptr == argv[0]) {
		Tcl_AppendResult(interp, "Bad x value: ", argv[0],
		    (char *)NULL);
		return (TCL_ERROR);
	}
	y = (int)strtol(argv[1], &chk_ptr, 0);
	if (chk_ptr == argv[1]) {
		Tcl_AppendResult(interp, "Bad y value: ", argv[1],
		    (char *)NULL);
		return (TCL_ERROR);
	}
	if (x < dgPtr->leftHit || x > dgPtr->rightHit) {
		Tcl_AppendResult(interp, "bad x value - not on screen",
		    (char *)NULL);
		return (TCL_ERROR);
	}
	if (y < dgPtr->topLabel || y > dgPtr->bottomLabel) {
		Tcl_AppendResult(interp, "bad y value - not on screen",
		    (char *)NULL);
		return (TCL_ERROR);
	}
	DrawHit(dgPtr, Tk_WindowId(dgPtr->tkwin), x, y, depressed);
	return (TCL_OK);
}

static int
AddLabel(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	int height;
	int i;
	int j;

	if (argc != 1) {
		Tcl_AppendResult(interp, "Must specify a label name",
		    (char *)NULL);
		return (TCL_ERROR);
	}

	if (dgPtr->labels == NULL) {
		if ((dgPtr->labels = (char **)ckalloc(sizeof(char *) *
		    (dgPtr->numLabels + 1))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory for ",
			    "for new label", (char *)NULL);
			return (TCL_ERROR);
		}
	} else {
		if ((dgPtr->labels = (char **)ckrealloc(dgPtr->labels,
		    sizeof(char *) * (dgPtr->numLabels + 1))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory for ",
			    "for new label", (char *)NULL);
			return (TCL_ERROR);
		}
	}
	if ((dgPtr->labels[dgPtr->numLabels] = ckstrdup(argv[0])) == NULL) {
		Tcl_AppendResult(interp, "Couldn't dup label name",
		    (char *)NULL);
		return (TCL_ERROR);
	}

	/* new Voice */
	if (dgPtr->voices == NULL) {
		if ((dgPtr->voices = (Voice *)ckalloc(sizeof(Voice) *
		    dgPtr->numLabels)) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory ",
			    "for voices", (char *)NULL);
			return (TCL_ERROR);
		}
		/* make hits(quanta) inside voices */
		for (i = 0; i < dgPtr->numLabels; i++) {
			if ((dgPtr->voices[i].volume =
			    (short *)ckalloc(dgPtr->numHits * sizeof(short)))
			    == NULL) {
				Tcl_AppendResult(interp, "Not enough memory ",
				    "for hits in voice", (char *)NULL);
				return (TCL_ERROR);
			}
			for (j = 0; j < dgPtr->numHits; j++)
				dgPtr->voices[i].volume[j] = 0;
		}
	} else {
		if ((dgPtr->voices = (Voice *)ckrealloc(dgPtr->voices,
		    sizeof(Voice) * (dgPtr->numLabels + 1))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory ",
			    "for voices", (char *)NULL);
			return (TCL_ERROR);
		}
		/* make hits(quanta) inside new voice */
		if ((dgPtr->voices[dgPtr->numLabels].volume =
		    (short *)ckalloc(dgPtr->numHits * sizeof(short))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory ",
			    "for hits in voice", (char *)NULL);
			return (TCL_ERROR);
		}
		for (j = 0; j < dgPtr->numHits; j++)
			dgPtr->voices[dgPtr->numLabels].volume[j] = 0;
	}

	/* new pitch */
	if (dgPtr->pitches == NULL) {
		if ((dgPtr->pitches = (int *)ckalloc(dgPtr->numLabels + 1 *
		    sizeof(int))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory for ",
			    "new pitch", (char *)NULL);
			return (TCL_ERROR);
		}
		for (j = 0; j < dgPtr->numLabels + 1; j++)
			dgPtr->pitches[j] = 0;
	} else {
		if ((dgPtr->pitches = (int *)ckrealloc(dgPtr->pitches,
		    sizeof(int) * (dgPtr->numLabels + 1))) == NULL) {
			Tcl_AppendResult(interp, "Note enought memory for ",
			    "new pitch", (char *)NULL);
			return (TCL_ERROR);
		}
		dgPtr->pitches[dgPtr->numLabels] = 0;
	}

	/* we also need extra bearings */
	if (dgPtr->leftBearings == NULL) {
		if ((dgPtr->leftBearings = (int *)ckalloc(dgPtr->numLabels + 1
		    * sizeof(int))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory for ",
			    "leftBearings", (char *)NULL);
			return (TCL_ERROR);
		}
	} else {
		if ((dgPtr->leftBearings = (int *)ckrealloc(dgPtr->leftBearings,
		    sizeof(int) * (dgPtr->numLabels + 1))) == NULL) {
			Tcl_AppendResult(interp, "Note enought memory for ",
			    "leftBearings", (char *)NULL);
			return (TCL_ERROR);
		}
	}
	if (dgPtr->rightBearings == NULL) {
		if ((dgPtr->rightBearings = (int *)ckalloc(dgPtr->numLabels + 1
		    * sizeof(int))) == NULL) {
			Tcl_AppendResult(interp, "Not enough memory for ",
			    "rightBearings", (char *)NULL);
			return (TCL_ERROR);
		}
	} else {
		if ((dgPtr->rightBearings =
		    (int *)ckrealloc(dgPtr->rightBearings, sizeof(int) *
		    (dgPtr->numLabels + 1))) == NULL) {
			Tcl_AppendResult(interp, "Note enought memory for ",
			    "rightBearings", (char *)NULL);
			return (TCL_ERROR);
		}
	}

	dgPtr->numLabels++;

	MakeLabelStr(dgPtr);
	MakePitchStr(dgPtr);
	if (AllocateGridLines(dgPtr) != TCL_OK)
		return (TCL_ERROR);

	/* we want to include new label in scrolling region */
	height = dgPtr->bottomLabel - dgPtr->topLabel;
	dgPtr->bottomLabel = dgPtr->numLabels - 1;
	dgPtr->topLabel = dgPtr->bottomLabel - height;
	dgPtr->flags |= UPDATE_YSCROLL;

	ComputeDrumGridGeometry(dgPtr);

	return (TCL_OK);
}

static int
RemoveLabel(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	int height;
	int i;
	int length;
	char *chkPtr;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Must specify \"label\" or \"index\"",
		    " followed by the label name or the index number",
		    (char *)NULL);
		return (TCL_ERROR);
	}

	length = strlen(argv[0]);
	if (strncmp(argv[0], "label", length) == 0) {
		for (i = 0; i < dgPtr->numLabels; i++)
			if (strcmp(dgPtr->labels[i], argv[1]) == 0)
				break;
		if (i == dgPtr->numLabels) {
			Tcl_AppendResult(interp, "No label \"", argv[1],
			    "\" found in drum grid", (char *)NULL);
			return (TCL_ERROR);
		}
	} else if (strncmp(argv[0], "index", length) == 0) {
		i = (int)strtol(argv[1], &chkPtr, 0);
		if (chkPtr == argv[1] || i < 0 || i >= dgPtr->numLabels) {
			Tcl_AppendResult(interp, "Bad index number ",
			    argv[1], (char *)NULL);
			return (TCL_ERROR);
		}
	} else {
		Tcl_AppendResult(interp, "Must specify \"label\" or \"index\".",
		    (char *)NULL);
		return (TCL_ERROR);
	}

	ckfree(dgPtr->labels[i]);
	ckfree((char *)dgPtr->voices[i].volume);

	/* move everything up one */
	for (; i < dgPtr->numLabels - 1; i++) {
		dgPtr->labels[i] = dgPtr->labels[i + 1];
		dgPtr->voices[i] = dgPtr->voices[i + 1];
		dgPtr->pitches[i] = dgPtr->pitches[i + 1];
	}

	if (dgPtr->numLabels - 1 == 0) {
		ckfree((char *)dgPtr->labels);
		dgPtr->labels = NULL;
	} else {
		if ((dgPtr->labels = (char **)ckrealloc(dgPtr->labels,
		    sizeof(char *) * (dgPtr->numLabels - 1))) == NULL) {
			Tcl_AppendResult(interp, "couldn't realloc label ",
			    "space", (char *)NULL);
			return (TCL_ERROR);
		}
	}

	/* one less voice */
	if (dgPtr->numLabels - 1 == 0) {
		ckfree((char *)dgPtr->voices);
		dgPtr->voices = NULL;
	} else {
		if ((dgPtr->voices = (Voice *)ckrealloc(dgPtr->voices,
		    sizeof(Voice) * (dgPtr->numLabels - 1))) == NULL) {
			Tcl_AppendResult(interp, "Couldn't realloc ",
			    "voices", (char *)NULL);
			return (TCL_ERROR);
		}
	}

	/* one less pitch */
	if (dgPtr->numLabels - 1 == 0) {
		ckfree((char *)dgPtr->pitches);
		dgPtr->pitches = NULL;
	} else {
		if ((dgPtr->pitches = (int *)ckrealloc(dgPtr->pitches,
		    sizeof(int) * (dgPtr->numLabels - 1))) == NULL) {
			Tcl_AppendResult(interp, "Couldn't realloc ",
			    "pitches", (char *)NULL);
			return (TCL_ERROR);
		}
	}
	
	/* one less on the bearings */
	if (dgPtr->numLabels - 1 == 0) {
		ckfree((char *)dgPtr->leftBearings);
		dgPtr->leftBearings = NULL;
	} else {
		if ((dgPtr->leftBearings = (int *)ckrealloc(dgPtr->leftBearings,
		    sizeof(int) * (dgPtr->numLabels - 1))) == NULL) {
			Tcl_AppendResult(interp, "Couldn't realloc ",
			    "leftBearings", (char *)NULL);
			return (TCL_ERROR);
		}
	}
	if (dgPtr->numLabels - 1 == 0) {
		ckfree((char *)dgPtr->rightBearings);
		dgPtr->rightBearings = NULL;
	} else {
		if ((dgPtr->rightBearings = (int *)
		    ckrealloc(dgPtr->rightBearings, sizeof(int) *
		    (dgPtr->numLabels - 1))) == NULL) {
			Tcl_AppendResult(interp, "Couldn't realloc ",
			    "rightBearings", (char *)NULL);
			return (TCL_ERROR);
		}
	}
	
	dgPtr->numLabels--;

	if (AllocateGridLines(dgPtr) != TCL_OK)
		return (TCL_ERROR);
	MakeLabelStr(dgPtr);
	MakePitchStr(dgPtr);

	/* make sure we stay in bounds */
	if (dgPtr->bottomLabel > dgPtr->numLabels - 1) {
		height = dgPtr->bottomLabel - dgPtr->topLabel;
		dgPtr->bottomLabel = dgPtr->numLabels - 1;
		dgPtr->topLabel = dgPtr->bottomLabel - height;
		dgPtr->flags |= UPDATE_YSCROLL;
	}

	ComputeDrumGridGeometry(dgPtr);

	return (TCL_OK);
}

static int
AllocateGridLines(dgPtr)
	DrumGrid *dgPtr;
{
	Tcl_Interp *interp = dgPtr->interp;

	if (dgPtr->vertLineColors != NULL)
		ckfree((char *)dgPtr->vertLineColors);
	if (dgPtr->vertLinePos != NULL)
		ckfree((char *)dgPtr->vertLinePos);
	if (dgPtr->horizLineColors != NULL)
		ckfree((char *)dgPtr->horizLineColors);
	if (dgPtr->horizLinePos != NULL)
		ckfree((char *)dgPtr->horizLinePos);

	if ((dgPtr->vertLineColors = (GC *)ckalloc(sizeof(GC) *
	    (dgPtr->numHits + 2))) == NULL) {
		Tcl_AppendResult(interp,
		    "Couldn't allocate new vertLineColors", (char *)NULL);
		return (TCL_ERROR);
	}
	if ((dgPtr->vertLinePos = (XSegment *)ckalloc(sizeof(XSegment) *
	    (dgPtr->rightHit - dgPtr->leftHit + 3))) == NULL) {
		Tcl_AppendResult(interp, "Couldn't allocate new vertLinePos",
		    (char *)NULL);
		return (TCL_ERROR);
	}
	if ((dgPtr->horizLineColors = (GC *)ckalloc(sizeof(GC) *
	    (dgPtr->numLabels + 1))) == NULL) {
		Tcl_AppendResult(interp,
		    "Couldn't allocate new horizLineColors", (char *)NULL);
		return (TCL_ERROR);
	}
	if ((dgPtr->horizLinePos = (XSegment *)ckalloc(sizeof(XSegment) *
	    (dgPtr->bottomLabel - dgPtr->topLabel + 2))) == NULL) {
		Tcl_AppendResult(interp, "Couldn't allocate new horizLinePos",
		    (char *)NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

static void
FitSize(dgPtr)
	DrumGrid *dgPtr;
{
	if (dgPtr->leftHit + dgPtr->resizedWidth <= dgPtr->numHits)
		dgPtr->rightHit = dgPtr->leftHit + dgPtr->resizedWidth - 1;
	else {
		dgPtr->rightHit = dgPtr->numHits - 1;
		if ((dgPtr->leftHit = dgPtr->numHits - dgPtr->resizedWidth) < 0)
			dgPtr->leftHit = 0;
	}
	if (dgPtr->topLabel + dgPtr->resizedHeight <= dgPtr->numLabels)
		dgPtr->bottomLabel = dgPtr->topLabel + dgPtr->resizedHeight - 1;
	else {
		dgPtr->bottomLabel = dgPtr->numLabels - 1;
		if ((dgPtr->topLabel = dgPtr->numLabels - dgPtr->resizedHeight)
		    < 0)
			dgPtr->topLabel = 0;
	}
}

static void
ChangeDrumGridLeft(dgPtr, index)
	DrumGrid *dgPtr;
	int index;
{
	int width;

	width = dgPtr->rightHit - dgPtr->leftHit + 1;

	if (index > dgPtr->numHits - width)
		index = dgPtr->numHits - width;
	if (index < 0)
		index = 0;

	if (dgPtr->leftHit != index) {
		dgPtr->leftHit = index;
		dgPtr->rightHit = index + width - 1;
		if (!(dgPtr->flags & REDRAW_PENDING)) {
			Tk_DoWhenIdle(DisplayDrumGrid, (ClientData)dgPtr);
			dgPtr->flags |= REDRAW_PENDING;
		}
		dgPtr->flags |= UPDATE_XSCROLL;
	}
}

static void
ChangeDrumGridTop(dgPtr, index)
	DrumGrid *dgPtr;
	int index;
{
	int height;

	height = dgPtr->bottomLabel - dgPtr->topLabel + 1;

	if (index > dgPtr->numLabels - height)
		index = dgPtr->numLabels - height;
	if (index < 0)
		index = 0;

	if (dgPtr->topLabel != index) {
		dgPtr->topLabel = index;
		dgPtr->bottomLabel = index + height - 1;
		if (!(dgPtr->flags & REDRAW_PENDING)) {
			Tk_DoWhenIdle(DisplayDrumGrid, (ClientData)dgPtr);
			dgPtr->flags |= REDRAW_PENDING;
		}
		dgPtr->flags |= UPDATE_YSCROLL;
	}
}

static void
DrumGridUpdateXScroll(dgPtr)
	DrumGrid *dgPtr;
{
	char string[60];
	int num_showing;

	if (dgPtr->xScrollCmd == NULL)
		return;

	num_showing = dgPtr->rightHit - dgPtr->leftHit + 1;
	sprintf(string, " %d %d %d %d", dgPtr->numHits, num_showing,
	    dgPtr->leftHit, dgPtr->rightHit);

	if (Tcl_VarEval(dgPtr->interp, dgPtr->xScrollCmd, string,
	    (char *)NULL) != TCL_OK)
		Tk_BackgroundError(dgPtr->interp);
}

static void
DrumGridUpdateYScroll(dgPtr)
	DrumGrid *dgPtr;
{
	char string[60];
	int num_showing;

	if (dgPtr->yScrollCmd == NULL)
		return;

	num_showing = dgPtr->bottomLabel - dgPtr->topLabel + 1;
	sprintf(string, " %d %d %d %d", dgPtr->numLabels, num_showing,
	    dgPtr->topLabel, dgPtr->bottomLabel);

	if (Tcl_VarEval(dgPtr->interp, dgPtr->yScrollCmd, string,
	    (char *)NULL) != TCL_OK)
		Tk_BackgroundError(dgPtr->interp);
}

static int
GetLabels(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	int i;

	for (i = 0; i < dgPtr->numLabels; i++)
		Tcl_AppendElement(interp, dgPtr->labels[i], 0);

	return (TCL_OK);
}

static int
ListPitches(interp, dgPtr, argc, argv)
	Tcl_Interp *interp;
	DrumGrid *dgPtr;
	int argc;
	char **argv;
{
	int i;
	char dummy[10];

	for (i = 0; i < dgPtr->numLabels; i++) {
		sprintf(dummy, "%d", dgPtr->pitches[i]);
		Tcl_AppendElement(interp, dummy, 0);
	}

	return (TCL_OK);
}

static void
MakeLabelStr(dgPtr)
	DrumGrid *dgPtr;
{
	Tcl_Interp *temp_interp;
	int i;

	temp_interp = Tcl_CreateInterp();
	for (i = 0; i < dgPtr->numLabels; i++)
		Tcl_AppendElement(temp_interp, dgPtr->labels[i], 0);

	if (dgPtr->labelStr != NULL)
		ckfree(dgPtr->labelStr);
	dgPtr->labelStr = ckstrdup(temp_interp->result);

	Tcl_DeleteInterp(temp_interp);
}

static void
MakePitchStr(dgPtr)
	DrumGrid *dgPtr;
{
	Tcl_Interp *temp_interp;
	int i;
	char dummy[10];

	temp_interp = Tcl_CreateInterp();
	for (i = 0; i < dgPtr->numLabels; i++) {
		sprintf(dummy, "%d", dgPtr->pitches[i]);
		Tcl_AppendElement(temp_interp, dummy, 0);
	}

	if (dgPtr->labelStr != NULL)
		ckfree(dgPtr->pitchStr);
	dgPtr->pitchStr = ckstrdup(temp_interp->result);

	Tcl_DeleteInterp(temp_interp);
}

char *
Tclm_DbCkstrdup(str)
	char *str;
{
	char *new_str;

	if (str == NULL)
		return (NULL);
	if ((new_str = ckalloc(strlen(str) + 1)) == NULL)
		return (NULL);
	strcpy(new_str, str);
	return (new_str);
}
