/**
 * Copyright 1993 Network Computing Devices, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name Network Computing Devices, Inc. not be
 * used in advertising or publicity pertaining to distribution of this
 * software without specific, written prior permission.
 *
 * THIS SOFTWARE IS PROVIDED `AS-IS'.  NETWORK COMPUTING DEVICES, INC.,
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT
 * LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NONINFRINGEMENT.  IN NO EVENT SHALL NETWORK
 * COMPUTING DEVICES, INC., BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING
 * SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF USE, DATA,
 * OR PROFITS, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF
 * WHETHER IN AN ACTION IN CONTRACT, TORT OR NEGLIGENCE, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author:	Greg Renda <greg@ncd.com>
 * 		Network Computing Devices, Inc.
 * 		350 North Bernardo Ave.
 * 		Mountain View, CA  94043
 *
 * $NCDId: @(#)audemo.c,v 1.60 1995/12/06 01:08:33 greg Exp $
 */
#include "config.h"

#include <stdio.h>

#if defined(HAVE_STDLIB_H)
# include <stdlib.h> 
#endif

#if defined(HAVE_MALLOC_H)
# include <malloc.h>
#endif

#include <signal.h>
#if !defined(SYSV) || defined(WIN32)
#include <audio/Aos.h>		/* for string and other os stuff */
#endif
#include <audio/Afuncs.h>	/* for bcopy et. al. */
#include <audio/audiolib.h>
#include <audio/soundlib.h>

#if defined(SYSV) && !defined(WIN32)
#include <sys/time.h>		/* for timeval */

/* not defined in <stdio.h> when -Xc used */
extern FILE    *
popen(
#if NeedFunctionPrototypes
      const char *, const char *
#endif
);
#endif						/* SVR4 */

#ifdef WIN32
#include <winsock.h>
#endif /* WIN32 */

#define AddToLinkedList(head, item)					       \
{									       \
    (item)->prev = NULL;						       \
    (item)->next = head;						       \
    if (head)								       \
	(head)->prev = item;						       \
    head = item;							       \
}

#define RemoveFromLinkedList(head, item)				       \
{									       \
    if ((item)->next)							       \
	(item)->next->prev = (item)->prev;				       \
									       \
    if ((item)->prev)							       \
	(item)->prev->next = (item)->next;				       \
    else								       \
	head = (item)->next;						       \
}

#define XT

#ifdef XT
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Cardinals.h>
#include <audio/Xtutil.h>

/* widgets */
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/Toggle.h>
#endif						/* XT */

#define APP_INSTANCE		"audemo"
#define	APP_CLASS		"Audemo"
#define	VOLUME_FORMAT		"Volume: %3d%%"
#define	GAIN_FORMAT		"Gain: %3d%%"
#define	DEFAULT_VOLUME		100
#define BUF_SIZE 		200
#define	MAX_VOLUME		200
#define	MIN_VOLUME		1
#define	MAX_GAIN		100
#define	MIN_GAIN		1
#define	DOUBLE_CLICK_TIME	500000		/* in microseconds */

#define VOL			AuFixedPointFromFraction(globals->volume, 100)
#define GAIN			AuFixedPointFromSum(globals->rec.gain, 0)

#define BUCKET_HEADER_FORMAT \
"%-20s  %5.2f     %2d     %5d Hz     %c      %s"

#define MakePopup(_w, _parent, _type, _name)				       \
{									       \
    (_w) =								       \
	XtCreatePopupShell(_name, _type, _parent, NULL, 0);		       \
}

#define MakeWidget(_w, _parent, _type, _callback, _name)		       \
{									       \
    (_w) = XtCreateManagedWidget(_name, _type, _parent, NULL, 0);	       \
									       \
    if ((void *) (_callback) != NULL)					       \
	XtAddCallback(_w, XtNcallback, _callback, g);			       \
}

#define MakeButton(_w, _parent, _callback, _name)			       \
    MakeWidget(_w, _parent, commandWidgetClass, _callback, _name)

#define MakeLabel(_w, _parent, _name)					       \
    MakeWidget(_w, _parent, labelWidgetClass, NULL, _name)

#define MakeMenuItem(_parent, _label)					       \
    XtCreateManagedWidget(_label, smeBSBObjectClass, _parent, NULL, 0)

static String   defaultResources[] =
{
#include "resources.h"
    NULL
};

static char   **makeFileList(char **fileNames, int nfiles);
static int      getFileNames(char *dir, char ***fileNames, char *template);
static void     fatalError(const char *message, const char *arg), 
                bucketQueryCB(Widget w, XtPointer globalsp, XtPointer call_data),
                bucketRecordDismissCB(Widget w, XtPointer globalsp, XtPointer call_data),
                meterCB();

#ifdef XT
#define Invert(w)							       \
{									       \
    Pixel fg, bg;							       \
									       \
    XtVaGetValues(w, XtNforeground, &fg, XtNbackground, &bg, NULL);	       \
    XtVaSetValues(w, XtNforeground, bg, XtNbackground, fg, NULL);	       \
}

typedef struct
{
    Widget          popShell,
                    file,
                    fileFormatMenuButton,
                    form;
}               SaveDialogDataRec, *SaveDialogDataPtr;

typedef struct
{
    Widget          popShell,
                    form,
                    record,
                    dismiss,
                    file,
                    fileFormatMenuButton,
                    dataFormatMenuButton,
                    rate,
                    monitor,
                    comment,
                    duration,
                    new,
                    readOnly,
                    mode,
                    gainLabel,
                    gainBar;
    unsigned int    gain;
}               RecordDialogDataRec, *RecordDialogDataPtr;

typedef struct
{
    Widget          popShell,
                    form,
                    dismiss,
                    viewport,
                    bucketList,
                    query,
                    play,
                    record,
                    load,
                    delete,
                    save,
                    formatMenu,
                    formatMenuButton,
                    accessMenu,
                    accessMenuButton;
    int             numBuckets;
    AuBucketAttributes *list;
    char          **bucketText;
}               BucketDialogDataRec, *BucketDialogDataPtr;

typedef struct
{
    Widget          topLevel,
                    meter,
                    quit,
                    form,
                    play,
                    samples,
                    info,
                    viewport,
                    volumeBar,
                    volumeLabel,
                    leftMeter,
                    rightMeter,
                    record,
                    buckets,
                    directory,
                    template,
                    rescan;
    int             volume,
                    numFiles,
                    inputDeviceNum,
                    sourceIndex;
    AuServer       *aud;
    char          **fileNames;
    Display        *dpy;
    RecordDialogDataRec rec;
    BucketDialogDataRec buf;
    SaveDialogDataRec save;
    AuDeviceID      inputDeviceId;
}               GlobalDataRec, *GlobalDataPtr;

typedef struct _ElementList
{
    AuFlowID        flow;
    int             volumeElement,
                    monitorElement;
    struct _ElementList *prev,
                   *next;
}               ElementListRec, *ElementListPtr;

typedef ElementListPtr ElementListId;

static ElementListPtr ElementList;
static int      ElementCount,
                MonitorCount;

#if !defined(sun) && !defined(linux) && !defined(__GNU__) && !defined(__GLIBC__)		/* who else doesn't have
						 * this? */
#define strdup ncd_strdup			/* To avoid conflicting with
						 * headers on hosts that *do*
						 * have strdup... */
char           *
strdup(char *s)
{
    char           *t;

    if (!s)
	return s;
    t = (char *) malloc(strlen(s) + 1);
    strcpy(t, s);
    return t;
}
#endif

static void
RemoveFromElementList(GlobalDataPtr globals, ElementListPtr p)
{
    RemoveFromLinkedList(ElementList, p);
    ElementCount--;

    if (p->monitorElement != -1)
    {
	MonitorCount--;

	if (!MonitorCount)
	    meterCB(globals->aud, NULL, NULL, globals);
    }

    free(p);
}

static          ElementListId
AddToElementList(AuFlowID flow, int volumeElement, int monitorElement)
{
    ElementListPtr  p;

    if (!(p = (ElementListPtr) malloc(sizeof(ElementListRec))))
	fatalError("malloc error in AddToElementList", NULL);

    p->flow = flow;
    p->volumeElement = volumeElement;
    p->monitorElement = monitorElement;

    AddToLinkedList(ElementList, p);
    ElementCount++;

    if (monitorElement != -1)
	MonitorCount++;

    return (ElementListId) p;
}

static void
queryInputAttributes(GlobalDataPtr globals)
{
    Boolean         mode;
    AuDeviceAttributes *attr;
    char            buf[50];

    attr = AuGetDeviceAttributes(globals->aud, globals->inputDeviceId, NULL);
    globals->rec.gain = AuFixedPointRoundUp(AuDeviceGain(attr));
    sprintf(buf, GAIN_FORMAT, globals->rec.gain);
    XtVaSetValues(globals->rec.gainLabel, XtNlabel, buf, NULL);
    XawScrollbarSetThumb(globals->rec.gainBar,
			 (float) globals->rec.gain / MAX_GAIN, -1.0);

    mode = AuDeviceLineMode(attr) == AuDeviceLineModeHigh;
    XtVaSetValues(globals->rec.mode, XtNstate, mode, NULL);

    AuFreeDeviceAttributes(globals->aud, 1, attr);
}

static void
meterToggleCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    Boolean         meter;
    AuElementState *states,
                   *s;
    ElementListPtr  p;
    int             state;

    XtVaGetValues(globals->meter, XtNstate, &meter, NULL);

    state = meter ? AuStateStart : AuStateStop;

    if (MonitorCount)
    {
	if (!(states = (AuElementState *) malloc(sizeof(AuElementState) *
						 MonitorCount)))
	    return;

	p = ElementList;
	s = states;

	while (p)
	{
	    if (p->monitorElement != -1)
	    {
		AuMakeElementState(s, p->flow, p->monitorElement, state);
		s++;
	    }

	    p = p->next;
	}

	AuSetElementStates(globals->aud, MonitorCount, states, NULL);
	free(states);
	meterCB(globals->aud, NULL, NULL, globals);
    }
}

#define MonitorNotifyEventMin(e, i) ((short *) &(e)->data)[i * (e)->num_fields]
#define MonitorNotifyEventMax(e, i)					       \
    ((short *) &(e)->data)[i * (e)->num_fields + 1]

static void meterCB(AuServer *aud, AuEventHandlerRec *handler, AuEvent *ev,
                    GlobalDataPtr globals)
{
    AuMonitorNotifyEvent *e = (AuMonitorNotifyEvent *) ev;
    static int      count;
    static float    left,
                    right;
    float           l;
    Boolean         meter;


    if (!ev)
    {
	XawScrollbarSetThumb(globals->leftMeter, -1.0, 0);
	XawScrollbarSetThumb(globals->rightMeter, -1.0, 0);
	count = left = right = 0;
	return;
    }

    XtVaGetValues(globals->meter, XtNstate, &meter, NULL);

    if (!meter)
	return;

    l = (float) (MonitorNotifyEventMax(e, 0) -
		 MonitorNotifyEventMin(e, 0)) / 65535;

    left += l;

    if (e->num_tracks == 1)
	right += l;
    else
	right += (float) (MonitorNotifyEventMax(e, 1) -
			  MonitorNotifyEventMin(e, 1)) / 65535;

    count++;

    if (count >= MonitorCount)
    {
	left /= MonitorCount;
	right /= MonitorCount;

	XawScrollbarSetThumb(globals->leftMeter, -1.0, left);
	XawScrollbarSetThumb(globals->rightMeter, -1.0, right);
	count = left = right = 0;
    }
}

typedef struct
{
    GlobalDataPtr   globals;
    void            (*callback) ();
}               DonePrivRec, *DonePrivPtr;

static void
doneCB(AuServer *aud, AuEventHandlerRec *handler, AuEvent *ev, AuPointer datap)
{
    DonePrivPtr     data = (DonePrivPtr) datap;

    if (ev->type == AuEventTypeMonitorNotify)
	meterCB(aud, handler, ev, data->globals);
    else
	(*data->callback) (NULL, data->globals, data);
}

static void
modeCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    Boolean         mode;
    AuDeviceAttributes a;

    XtVaGetValues(globals->rec.mode, XtNstate, &mode, NULL);
    AuDeviceValueMask(&a) = AuCompDeviceLineModeMask;
    AuDeviceLineMode(&a) = mode ? AuDeviceLineModeHigh : AuDeviceLineModeLow;
    AuSetDeviceAttributes(globals->aud, globals->inputDeviceId,
			  AuCompDeviceLineModeMask, &a, NULL);
}

static void
newBucketCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    String          s;
    unsigned int    rate,
                    format;
    Boolean         readOnly;
    AuString        desc;
    float           maxDuration;

    XtVaGetValues(globals->rec.rate, XtNstring, &s, NULL);
    rate = atoi(s);
    XtVaGetValues(globals->rec.duration, XtNstring, &s, NULL);
    sscanf(s, "%f", &maxDuration);
    XtVaGetValues(globals->rec.readOnly, XtNstate, &readOnly, NULL);
    XtVaGetValues(globals->rec.dataFormatMenuButton, XtNlabel, &s, NULL);
    format = AuStringToFormat(s);
    XtVaGetValues(globals->rec.comment, XtNstring, &s, NULL);

    desc.type = AuStringLatin1;
    desc.len = strlen(s);
    desc.data = s;

    AuCreateBucket(globals->aud, format, 1,
		   readOnly ? AuAccessImportMask | AuAccessListMask :
		   AuAccessAllMasks, rate,
		   (AuUint32) (maxDuration * rate), &desc, NULL);

    bucketQueryCB(globals->buf.query, globals, NULL);
}

static void
rescanCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    static char    *noFilesString = "No files found";
    char           *dir,
                   *template;
    int             i;

    if (globals->numFiles)
    {
	for (i = 0; i < globals->numFiles; i++)
	    free(globals->fileNames[i]);

	free(globals->fileNames);
    }

    XtVaGetValues(globals->directory, XtNstring, &dir, NULL);
    XtVaGetValues(globals->template, XtNstring, &template, NULL);
    globals->numFiles = getFileNames(dir, &globals->fileNames, template);

    if (globals->numFiles)
    {
	XawListChange(globals->samples,
		      makeFileList(globals->fileNames, globals->numFiles),
		      globals->numFiles, -1, True);
	XtSetSensitive(globals->samples, True);
    }
    else
    {
	XawListChange(globals->samples, &noFilesString, 1, -1, True);
	XtSetSensitive(globals->samples, False);
    }
}

static void
bucketPlayCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    static Boolean  playing;
    static AuFlowID flow;
    static int      multiplier;
    static DonePrivRec priv;
    static ElementListId listid;
    XawListReturnStruct *sel;
    BucketDialogDataPtr buf = &globals->buf;
    Boolean         meter;
    int             ind,
                    monitor = -1;

    if (playing)
    {
	if (w)
	    /* user requested stop */
	    AuStopFlow(globals->aud, flow, NULL);
	else
	{
	    /* got a done callback */
	    RemoveFromElementList(globals, listid);
	    playing = False;
	    Invert(globals->buf.play);
	}

	return;
    }
    else if (w != globals->buf.play)
	return;

    sel = XawListShowCurrent(buf->bucketList);

    if ((ind = sel->list_index) == -1)
	return;

    priv.globals = globals;
    priv.callback = bucketPlayCB;

    XtVaGetValues(globals->meter, XtNstate, &meter, NULL);

    if (AuSoundPlayFromBucket(globals->aud,
			      AuBucketIdentifier(&buf->list[ind]), AuNone,
			      VOL, doneCB, &priv, 1, &flow, &multiplier,
			      meter ? &monitor : NULL, NULL))
    {
	listid = AddToElementList(flow, multiplier, monitor);
	playing = True;
	Invert(globals->buf.play);
    }
}

#define COMMENT_LEN 20

static void
bucketQueryCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    BucketDialogDataPtr buf = &globals->buf;
    char            tmp[200],
                    access[4],
                    desc[COMMENT_LEN];
    int             i;
    static char    *noBucketString = "No buckets";

    if (buf->numBuckets)
    {
	AuFreeBucketAttributes(globals->aud, buf->numBuckets, buf->list);

	for (i = 0; i < buf->numBuckets; i++)
	    free(buf->bucketText[i]);

	free(buf->bucketText);
    }

    buf->list = AuListBuckets(globals->aud, 0, NULL, &buf->numBuckets, NULL);

    if (buf->numBuckets &&
    !(buf->bucketText = (char **) malloc(sizeof(char *) * buf->numBuckets)))
	fatalError("malloc error in bucketQueryCB", NULL);

    for (i = 0; i < buf->numBuckets; i++)
    {
	AuBucketAttributes *p;
	int             j;
	char            format;

	p = &buf->list[i];
	*desc = 0;
	*access = 0;

	if (AuBucketValueMask(p) & AuCompCommonDescriptionMask)
	{
	    strncpy(desc, AuBucketDescription(p)->data, COMMENT_LEN - 1);
	    desc[COMMENT_LEN - 1] = 0;
	}

	if (AuBucketAccess(p) & AuAccessImportMask)
	    strcat(access, "I");
	if (AuBucketAccess(p) & AuAccessExportMask)
	    strcat(access, "E");
	if (AuBucketAccess(p) & AuAccessDestroyMask)
	    strcat(access, "D");

	for (j = 0, format = 'A'; j < AuServerNumFormats(globals->aud);
	     j++, format++)
	    if (AuBucketFormat(p) == AuServerFormat(globals->aud, j))
		break;

	sprintf(tmp, BUCKET_HEADER_FORMAT,
		desc, (float) AuBucketNumSamples(p) / AuBucketSampleRate(p),
		AuBucketNumTracks(p), AuBucketSampleRate(p), format, access);

	buf->bucketText[i] = strdup(tmp);
    }

    if (buf->numBuckets)
    {
	XawListChange(buf->bucketList, buf->bucketText, buf->numBuckets, -1,
		      True);
	XtSetSensitive(buf->bucketList, True);
    }
    else
    {
	XawListChange(buf->bucketList, &noBucketString, 1, -1, True);
	XtSetSensitive(buf->bucketList, False);
    }
}

static void
bucketDeleteCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    XawListReturnStruct *sel;
    BucketDialogDataPtr buf = &globals->buf;
    int             ind;

    sel = XawListShowCurrent(buf->bucketList);

    if ((ind = sel->list_index) == -1)
	return;

    AuDestroyBucket(globals->aud, AuBucketIdentifier(&buf->list[ind]), NULL);
    bucketQueryCB(buf->query, globals, NULL);
}

static void
bucketLoadCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    XawListReturnStruct *sel;
    int             ind;

    sel = XawListShowCurrent(globals->samples);

    if ((ind = sel->list_index) == -1)
	return;

    AuSoundCreateBucketFromFile(globals->aud, globals->fileNames[ind],
				AuAccessAllMasks, NULL, NULL);
    bucketQueryCB(globals->buf.query, globals, NULL);
}

static void
bucketsCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;

    bucketQueryCB(globals->buf.query, globals, 0);
    XtSetSensitive(w, False);
    XtPopup(globals->buf.popShell, XtGrabNone);
}

static void
bucketRecordStartCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    static Boolean  recording;
    static DonePrivRec priv;
    static AuFlowID flow;
    Boolean         mode;
    XawListReturnStruct *sel;
    BucketDialogDataPtr buf = &globals->buf;
    int             ind;

    if (recording)
    {
	if (w)
	    /* user requested stop */
	    AuStopFlow(globals->aud, flow, NULL);
	else
	{
	    /* got a done callback */
	    recording = False;
	    Invert(globals->rec.record);
	}

	return;
    }
    else if (w != globals->rec.record)
	return;

    sel = XawListShowCurrent(buf->bucketList);

    if ((ind = sel->list_index) == -1)
	return;

    priv.globals = globals;
    priv.callback = bucketRecordStartCB;

    XtVaGetValues(globals->rec.mode, XtNstate, &mode, NULL);

    if (AuSoundRecordToBucket(globals->aud,
			      AuBucketIdentifier(&buf->list[ind]),
			      globals->inputDeviceId, GAIN, doneCB, &priv,
			  mode ? AuDeviceLineModeHigh : AuDeviceLineModeLow,
			      &flow, NULL, NULL))
    {
	Invert(globals->rec.record);
	recording = True;
    }
}

static void
recordStartCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    static Boolean  recording;
    static DonePrivRec priv;
    static AuFlowID flow;
    String          s,
                    comment;
    AuUint32        rate,
                    dataFormat,
                    fileFormat;
    Boolean         mode;

    if (recording)
    {
	if (w)
	    /* user requested stop */
	    AuStopFlow(globals->aud, flow, NULL);
	else
	{
	    /* got a done callback */
	    recording = False;
	    Invert(globals->rec.record);
	    rescanCB(globals->rescan, globals, 0);
	}

	return;
    }
    else if (w != globals->rec.record)
	return;

    priv.globals = globals;
    priv.callback = recordStartCB;

    XtVaGetValues(globals->rec.fileFormatMenuButton, XtNlabel, &s, NULL);
    fileFormat = SoundStringToFileFormat(s);

    XtVaGetValues(globals->rec.dataFormatMenuButton, XtNlabel, &s, NULL);
    dataFormat = AuStringToFormat(s);

    XtVaGetValues(globals->rec.rate, XtNstring, &s, NULL);
    rate = atoi(s);

    XtVaGetValues(globals->rec.mode, XtNstate, &mode, NULL);
    XtVaGetValues(globals->rec.comment, XtNstring, &comment, NULL);
    XtVaGetValues(globals->rec.file, XtNstring, &s, NULL);

    if (strlen(s) &&
	AuSoundRecordToFile(globals->aud, s, globals->inputDeviceId, GAIN,
			    doneCB, &priv,
			    mode ? AuDeviceLineModeHigh :
			    AuDeviceLineModeLow,
			    fileFormat, comment, rate, dataFormat,
			    &flow, NULL, NULL))
    {
	Invert(globals->rec.record);
	recording = True;
    }
}

static void
monitorCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    static Boolean  monitoring;
    static AuFlowID flow;
    static int      multiplier;
    static DonePrivRec priv;
    static ElementListId listid;
    String          s;
    Boolean         meter;
    int             rate,
                    monitor = -1;

    if (monitoring)
    {
	if (w)
	    /* user requested stop */
	    AuStopFlow(globals->aud, flow, NULL);
	else
	{
	    /* got a done callback */
	    RemoveFromElementList(globals, listid);
	    monitoring = False;
	    Invert(globals->rec.monitor);
	}

	return;
    }
    else if (w != globals->rec.monitor)
	return;

    XtVaGetValues(globals->rec.rate, XtNstring, &s, NULL);
    rate = atoi(s);

    priv.globals = globals;
    priv.callback = monitorCB;

    XtVaGetValues(globals->meter, XtNstate, &meter, NULL);

    if (AuMonitorDevice(globals->aud, rate, globals->inputDeviceId, AuNone, VOL,
			doneCB, &priv, &flow, &multiplier,
			meter ? &monitor : NULL, NULL))
    {
	listid = AddToElementList(flow, multiplier, monitor);
	monitoring = True;
	Invert(globals->rec.monitor);
    }
}

static void
saveCancel(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;

    XtPopdown(globals->save.popShell);
    XtSetSensitive(globals->buf.save, True);
}

static void
saveOk(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    String          s;
    XawListReturnStruct *sel;
    BucketDialogDataPtr buf = &globals->buf;
    int             ind,
                    fileFormat;

    sel = XawListShowCurrent(buf->bucketList);

    if ((ind = sel->list_index) == -1)
	return;

    XtVaGetValues(globals->save.fileFormatMenuButton, XtNlabel, &s, NULL);
    fileFormat = SoundStringToFileFormat(s);

    XtVaGetValues(globals->save.file, XtNstring, &s, NULL);

    if (strlen(s) &&
	AuSoundCreateFileFromBucket(globals->aud, s, fileFormat,
				    AuBucketIdentifier(&buf->list[ind]),
				    NULL))
    {
	saveCancel(w, globals, call_data);
	rescanCB(globals->rescan, globals, 0);
    }
}

static void
okAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    GlobalDataPtr   globals;

    /* retrieve the address of the globals from the first parameter */
    globals = (GlobalDataPtr) atoi(params[0]);
    saveOk(w, globals, 0);
}

static void
bucketSaveCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    XawListReturnStruct *sel;

    sel = XawListShowCurrent(globals->buf.bucketList);

    if (sel->list_index == -1)
	return;

    XtSetSensitive(w, False);
    XtPopup(globals->save.popShell, XtGrabNonexclusive);
}

static void
bucketDismissCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;

    if (globals->inputDeviceId)
	bucketRecordDismissCB(globals->rec.dismiss, globals, NULL);

    bucketPlayCB(NULL, globals, NULL);

    XtPopdown(globals->buf.popShell);
    XtSetSensitive(globals->buckets, True);
}

static void
recordDismissCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;

    monitorCB(w, globals, NULL);
    recordStartCB(w, globals, NULL);

    XtPopdown(globals->rec.popShell);
    XtSetSensitive(globals->record, True);
    XtSetSensitive(globals->buf.record, True);
}

static void
bucketRecordDismissCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;

    monitorCB(w, globals, NULL);
    bucketRecordStartCB(w, globals, NULL);

    if (globals->inputDeviceId)
    {
	XtPopdown(globals->rec.popShell);
	XtSetSensitive(globals->record, True);
	XtSetSensitive(globals->buf.record, True);
    }
}

#define ReplaceCallback(w, cb)						       \
{									       \
    XtRemoveAllCallbacks(w, XtNcallback);				       \
    XtAddCallback(w, XtNcallback, cb, globals);				       \
}

static void
recordSensitives(GlobalDataPtr globals, Boolean state)
{
    XtSetSensitive(globals->rec.file, state);
}

static void
bucketRecordSensitives(GlobalDataPtr globals, Boolean state)
{
    XtSetSensitive(globals->rec.duration, state);
    XtSetSensitive(globals->rec.readOnly, state);
    XtSetSensitive(globals->rec.new, state);
}

static void
recordCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;

    ReplaceCallback(globals->rec.record, recordStartCB);
    ReplaceCallback(globals->rec.dismiss, recordDismissCB);

    recordSensitives(globals, True);
    bucketRecordSensitives(globals, False);
    XtSetSensitive(globals->buf.record, False);

    queryInputAttributes(globals);

    XtSetSensitive(w, False);
    XtPopup(globals->rec.popShell, XtGrabNone);
}

static void
bucketRecordCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;

    ReplaceCallback(globals->rec.record, bucketRecordStartCB);
    ReplaceCallback(globals->rec.dismiss, bucketRecordDismissCB);

    recordSensitives(globals, False);
    bucketRecordSensitives(globals, True);
    XtSetSensitive(globals->record, False);

    queryInputAttributes(globals);

    XtSetSensitive(w, False);
    XtPopup(globals->rec.popShell, XtGrabNone);
}

static void
playCB(Widget w, XtPointer globalsp, XtPointer datap)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    static Boolean  playing;
    static AuFlowID flow;
    static int      multiplier;
    static DonePrivRec priv;
    static ElementListId listid;
    XawListReturnStruct *sel;
    Boolean         meter;
    int             ind,
                    monitor = -1;

    if (playing)
    {
	if (w)
	    /* user requested stop */
	    AuStopFlow(globals->aud, flow, NULL);
	else
	{
	    /* got a done callback */
	    RemoveFromElementList(globals, listid);
	    playing = False;
	    Invert(globals->play);
	}

	return;
    }

    sel = XawListShowCurrent(globals->samples);

    if ((ind = sel->list_index) == -1)
	return;

    priv.globals = globals;
    priv.callback = playCB;

    XtVaGetValues(globals->meter, XtNstate, &meter, NULL);

    if (AuSoundPlayFromFile(globals->aud, globals->fileNames[ind], AuNone,
			    VOL, doneCB, &priv, &flow, &multiplier,
			    meter ? &monitor : NULL, NULL))
    {
	listid = AddToElementList(flow, multiplier, monitor);
	playing = True;
	Invert(globals->play);
    }
}

static void
bucketListCB(Widget w, XtPointer globalsp, XtPointer listInfop)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    XawListReturnStruct *listInfo = (XawListReturnStruct *) listInfop;
    struct timeval  tp;
    AuUint32        currentTime;
    static AuInt32  lastTime;
    static int      lastSelection;

    gettimeofday(&tp, NULL);
    currentTime = tp.tv_sec * 1000000 + tp.tv_usec;

    if (listInfo->list_index == lastSelection &&
	currentTime - lastTime < DOUBLE_CLICK_TIME)
	bucketPlayCB(globals->buf.play, globals, NULL);

    lastTime = currentTime;
    lastSelection = listInfo->list_index;
}

static void
samplesCB(Widget w, XtPointer globalsp, XtPointer listInfop)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    XawListReturnStruct *listInfo = (XawListReturnStruct *) listInfop;
    struct timeval  tp;
    AuInt32         currentTime;
    static AuInt32  lastTime;
    static int      lastSelection = -1;

    gettimeofday(&tp, NULL);
    currentTime = tp.tv_sec * 1000000 + tp.tv_usec;

    if (listInfo->list_index == lastSelection &&
	currentTime - lastTime < DOUBLE_CLICK_TIME)
	playCB(globals->play, globals, (XtPointer) 0);
    else if (lastSelection != listInfo->list_index)
    {
	Sound           s;

	lastSelection = listInfo->list_index;

	if ((s = SoundOpenFileForReading(globals->fileNames[lastSelection])))
	{
	    char           *buf,
	                   *p;

#define PRINT(p, f, a)							       \
{									       \
    sprintf(p, f, a);							       \
    p += strlen(p);							       \
}

	    if (!(p = buf = (char *) malloc(2000 + strlen(SoundComment(s)))))
		fatalError("Can't malloc text in samplesCB", NULL);

	    PRINT(p, "   Filename: %s\n", globals->fileNames[lastSelection]);
	    PRINT(p, "File Format: %s\n", SoundFileFormatString(s));
	    PRINT(p, "Data Format: %s\n", SoundDataFormatString(s));
	    PRINT(p, "     Tracks: %d\n", SoundNumTracks(s));
	    PRINT(p, "  Frequency: %d Hz\n", SoundSampleRate(s));
	    PRINT(p, "   Duration: %.2f seconds\n",
		  (float) SoundNumSamples(s) / SoundSampleRate(s));

	    PRINT(p, "\n%s", SoundComment(s));

	    XtVaSetValues(globals->info, XtNstring, buf, NULL);

	    free(buf);
	    SoundCloseFile(s);
	}
    }

    gettimeofday(&tp, NULL);
    lastTime = tp.tv_sec * 1000000 + tp.tv_usec;
}

static void
quitCB(Widget w, XtPointer globalsp, XtPointer call_data)
{
    exit(0);
}

static void
adjustVolume(GlobalDataPtr globals)
{
    AuElementParameters *parms;
    ElementListPtr  p = ElementList;
    int             i = 0;

    if (!ElementCount)
	return;

    if (!(parms = (AuElementParameters *)
	  malloc(sizeof(AuElementParameters) * ElementCount)))
	fatalError("malloc error in adjustVolume", NULL);

    while (p)
    {
	parms[i].flow = p->flow;
	parms[i].element_num = p->volumeElement;
	parms[i].num_parameters = AuParmsMultiplyConstant;
	parms[i].parameters[AuParmsMultiplyConstantConstant] = VOL;

	p = p->next;
	i++;
    }

    AuSetElementParameters(globals->aud, ElementCount, parms, NULL);
    free(parms);
}

static void
scrollProcCB(Widget w, XtPointer globalsp, XtPointer positionp)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    int             position = (int) positionp;
    int             newVolume;
    char            buf[50];

    newVolume = globals->volume + (position > 0 ? -1 : 1);

    if (newVolume < MIN_VOLUME)
	newVolume = MIN_VOLUME;
    else if (newVolume > MAX_VOLUME)
	newVolume = MAX_VOLUME;

    if (newVolume != globals->volume)
    {
	globals->volume = newVolume;
	sprintf(buf, VOLUME_FORMAT, globals->volume);
	XtVaSetValues(globals->volumeLabel, XtNlabel, buf, NULL);

	XawScrollbarSetThumb(globals->volumeBar,
			     (float) globals->volume / MAX_VOLUME, -1.0);
	adjustVolume(globals);
    }
}

static void
jumpProcCB(Widget w, XtPointer globalsp, XtPointer percentp)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    float          *percent = (float *) percentp;
    int             newVolume;
    char            buf[50];

    newVolume = *percent * MAX_VOLUME;

    if (newVolume < MIN_VOLUME)
	newVolume = MIN_VOLUME;

    if (newVolume != globals->volume)
    {
	globals->volume = newVolume;
	sprintf(buf, VOLUME_FORMAT, globals->volume);
	XtVaSetValues(globals->volumeLabel, XtNlabel, buf, NULL);
	adjustVolume(globals);
    }
}

static void
adjustGain(GlobalDataPtr globals)
{
    AuDeviceAttributes a;

    AuDeviceValueMask(&a) = AuCompDeviceGainMask;
    AuDeviceGain(&a) = GAIN;
    AuSetDeviceAttributes(globals->aud, globals->inputDeviceId,
			  AuCompDeviceGainMask, &a, NULL);
}

static void
gainScrollCB(Widget w, XtPointer globalsp, XtPointer positionp)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    int             position = (int) positionp;
    int             newGain;
    char            buf[50];

    newGain = globals->rec.gain + (position > 0 ? -1 : 1);

    if (newGain < MIN_GAIN)
	newGain = MIN_GAIN;
    else if (newGain > MAX_GAIN)
	newGain = MAX_GAIN;

    if (newGain != globals->rec.gain)
    {
	globals->rec.gain = newGain;
	sprintf(buf, GAIN_FORMAT, globals->rec.gain);
	XtVaSetValues(globals->rec.gainLabel, XtNlabel, buf, NULL);

	XawScrollbarSetThumb(globals->rec.gainBar,
			     (float) globals->rec.gain / MAX_GAIN, -1.0);
	adjustGain(globals);
    }
}

static void
gainJumpCB(Widget w, XtPointer globalsp, XtPointer percentp)
{
    GlobalDataPtr   globals = (GlobalDataPtr) globalsp;
    float          *percent = (float *) percentp;
    int             newGain;
    char            buf[50];

    newGain = *percent * MAX_GAIN;

    if (newGain < MIN_GAIN)
	newGain = MIN_GAIN;

    if (newGain != globals->rec.gain)
    {
	globals->rec.gain = newGain;
	sprintf(buf, GAIN_FORMAT, globals->rec.gain);
	XtVaSetValues(globals->rec.gainLabel, XtNlabel, buf, NULL);
	adjustGain(globals);
    }
}

static void
setFileFormatMenuButton(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   g = (GlobalDataPtr) globalsp;
    String          string;

    XtVaGetValues(w, XtNlabel, &string, NULL);
    XtVaSetValues(g->save.fileFormatMenuButton, XtNlabel, string, NULL);

    if (g->inputDeviceId)
	XtVaSetValues(g->rec.fileFormatMenuButton, XtNlabel, string, NULL);
}

static void
setDataFormatMenuButton(Widget w, XtPointer globalsp, XtPointer call_data)
{
    GlobalDataPtr   g = (GlobalDataPtr) globalsp;
    String          string;

    XtVaGetValues(w, XtNlabel, &string, NULL);
    XtVaSetValues(g->rec.dataFormatMenuButton, XtNlabel, string, NULL);
}

static void
makeSaveDialog(GlobalDataPtr g)
{
    SaveDialogDataPtr s = &g->save;
    Widget          w;
    char            tmp[50];

    MakePopup(s->popShell, g->topLevel, transientShellWidgetClass,
	      "savePop");
    MakeWidget(s->form, s->popShell, formWidgetClass, NULL, "saveWin");

    /* filename */
    MakeLabel(w, s->form, "filenameLabel");
    MakeWidget(s->file, s->form, asciiTextWidgetClass, NULL, "filename");

    /* pass the address of the globals as an argument to the action */
    sprintf(tmp, "<Key>Return: ok(%u)", (unsigned int) g);
    XtOverrideTranslations(s->file, XtParseTranslationTable(tmp));

    /* file format */
    MakeLabel(w, s->form, "fileFormatLabel");
    MakeWidget(s->fileFormatMenuButton, s->form, menuButtonWidgetClass, NULL,
	       "fileFormatMenuButton");
    XtVaSetValues(s->fileFormatMenuButton, XtNlabel, SoundFileFormatToString(0),
		  NULL);

    MakeButton(w, s->form, saveOk, "ok");
    MakeButton(w, s->form, saveCancel, "cancel");
}

static void
makeBucketDialog(GlobalDataPtr g)
{
    Widget          w;
    int             i;
    BucketDialogDataPtr b = &g->buf;
    char            tmp[100];

    makeSaveDialog(g);

    b->numBuckets = 0;
    MakePopup(b->popShell, g->topLevel, transientShellWidgetClass,
	      "bucketPop");
    MakeWidget(b->form, b->popShell, formWidgetClass, NULL, "bucketWin");

    /* command buttons */
    MakeButton(b->query, b->form, bucketQueryCB, "query");
    MakeButton(b->play, b->form, bucketPlayCB, "play");
    MakeButton(b->record, b->form, bucketRecordCB, "record");
    MakeButton(b->load, b->form, bucketLoadCB, "load");
    MakeButton(b->save, b->form, bucketSaveCB, "save");
    MakeButton(b->delete, b->form, bucketDeleteCB, "delete");
    MakeButton(b->dismiss, b->form, bucketDismissCB, "dismiss");

    /* format menu */
    MakePopup(b->formatMenu, b->form, simpleMenuWidgetClass, "formatMenu");
    for (i = 0; i < AuServerNumFormats(g->aud); i++)
    {
	sprintf(tmp, "%c - %s", 'A' + i,
		AuFormatToString(AuServerFormat(g->aud, i)));
	MakeMenuItem(b->formatMenu, tmp);
    }
    MakeWidget(b->formatMenuButton, b->form, menuButtonWidgetClass, NULL,
	       "format");

    /* access menu */
    MakePopup(b->accessMenu, b->form, simpleMenuWidgetClass, "accessMenu");
    MakeMenuItem(b->accessMenu, "I - Importable");
    MakeMenuItem(b->accessMenu, "E - Exportable");
    MakeMenuItem(b->accessMenu, "D - Destroyable");
    MakeWidget(b->accessMenuButton, b->form, menuButtonWidgetClass, NULL,
	       "access");

    MakeLabel(w, b->form, "header");
    MakeWidget(b->viewport, b->form, viewportWidgetClass, NULL, "viewport");

    MakeWidget(b->bucketList, b->viewport, listWidgetClass, bucketListCB,
	       "list");
}

static void
makeRecordDialog(GlobalDataPtr g)
{
    Widget          w;
    RecordDialogDataPtr r = &g->rec;

    MakePopup(r->popShell, g->topLevel, transientShellWidgetClass, "recordPop");
    MakeWidget(r->form, r->popShell, formWidgetClass, NULL, "recordWin");

    /* filename */
    MakeLabel(w, r->form, "filenameLabel");
    MakeWidget(r->file, r->form, asciiTextWidgetClass, NULL, "filename");

    /* file format */
    MakeLabel(w, r->form, "fileFormatLabel");
    MakeWidget(r->fileFormatMenuButton, r->form, menuButtonWidgetClass, NULL,
	       "fileFormatMenuButton");
    XtVaSetValues(r->fileFormatMenuButton, XtNlabel, SoundFileFormatToString(0),
		  NULL);

    /* data format */
    MakeLabel(w, r->form, "dataFormatLabel");
    MakeWidget(r->dataFormatMenuButton, r->form, menuButtonWidgetClass, NULL,
	       "dataFormatMenuButton");
    XtVaSetValues(r->dataFormatMenuButton, XtNlabel,
		  AuFormatToString(AuServerFormat(g->aud, 0)), NULL);

    /* max duration */
    MakeLabel(w, r->form, "durationLabel");
    MakeWidget(r->duration, r->form, asciiTextWidgetClass, NULL, "duration");

    /* read only toggle */
    MakeWidget(r->readOnly, r->form, toggleWidgetClass, NULL, "readOnly");

    /* frequency */
    MakeLabel(w, r->form, "frequencyLabel");
    MakeWidget(r->rate, r->form, asciiTextWidgetClass, NULL, "frequency");

    /* mode toggle */
    MakeWidget(r->mode, r->form, toggleWidgetClass, modeCB, "lineMode");

    /* comment */
    MakeLabel(w, r->form, "commentLabel");
    MakeWidget(r->comment, r->form, asciiTextWidgetClass, NULL, "comment");

    /* gain */
    MakeLabel(r->gainLabel, r->form, "gainLabel");
    MakeWidget(r->gainBar, r->form, scrollbarWidgetClass, NULL, "gain");
    XtAddCallback(r->gainBar, XtNscrollProc, gainScrollCB, g);
    XtAddCallback(r->gainBar, XtNjumpProc, gainJumpCB, g);

    /* command buttons */
    MakeButton(r->record, r->form, recordStartCB, "record");
    MakeButton(r->monitor, r->form, monitorCB, "monitor");
    MakeButton(r->new, r->form, newBucketCB, "new");
    MakeButton(r->dismiss, r->form, NULL, "dismiss");
}

static void
createWidgets(GlobalDataPtr g, char *dir)
{
    Widget          w,
                    ww;
    int             i;
    char            buf[20];

    MakeWidget(g->form, g->topLevel, formWidgetClass, NULL, "mainWin");

    /* command buttons */
    MakeButton(g->play, g->form, playCB, "play");
    MakeButton(g->record, g->form, recordCB, "record");
    MakeButton(g->buckets, g->form, bucketsCB, "buckets");
    MakeWidget(g->meter, g->form, toggleWidgetClass, meterToggleCB,
	       "meterToggle");
    MakeButton(g->rescan, g->form, rescanCB, "rescan");
    MakeButton(g->quit, g->form, quitCB, "quit");

    /* version label */
    MakeLabel(w, g->form, "version");
    sprintf(buf, "NAS %d.%d", AuServerProtocolMajorVersion(g->aud),
	    AuServerProtocolMinorVersion(g->aud));
    XtVaSetValues(w, XtNlabel, buf, NULL);

    /* left meter */
    MakeLabel(w, g->form, "leftMeterLabel");
    MakeWidget(g->leftMeter, g->form, scrollbarWidgetClass, NULL, "leftMeter");

    /* right meter */
    MakeLabel(w, g->form, "rightMeterLabel");
    MakeWidget(g->rightMeter, g->form, scrollbarWidgetClass, NULL,
	       "rightMeter");

    /* volume label */
    MakeLabel(g->volumeLabel, g->form, "volumeLabel");
    sprintf(buf, VOLUME_FORMAT, g->volume);
    XtVaSetValues(g->volumeLabel, XtNlabel, buf, NULL);

    /* volume slider */
    MakeWidget(g->volumeBar, g->form, scrollbarWidgetClass, NULL, "volume");
    XawScrollbarSetThumb(g->volumeBar,
			 ((float) DEFAULT_VOLUME) / MAX_VOLUME, -1.0);
    XtAddCallback(g->volumeBar, XtNscrollProc, scrollProcCB, g);
    XtAddCallback(g->volumeBar, XtNjumpProc, jumpProcCB, g);

    /* info window */
    MakeWidget(g->info, g->form, asciiTextWidgetClass, NULL, "info");

    /* directory */
    MakeLabel(w, g->form, "directoryLabel");
    MakeWidget(g->directory, g->form, asciiTextWidgetClass, NULL, "directory");
    XtVaSetValues(g->directory, XtNstring, dir, NULL);

    /* template */
    MakeLabel(w, g->form, "templateLabel");
    MakeWidget(g->template, g->form, asciiTextWidgetClass, NULL, "template");

    /* samples window */
    MakeWidget(g->viewport, g->form, viewportWidgetClass, NULL, "viewport");
    MakeWidget(g->samples, g->viewport, listWidgetClass, samplesCB, "list");

    /* file format menu */
    MakePopup(w, g->topLevel, simpleMenuWidgetClass, "fileFormatMenu");

    for (i = 0; i < SoundNumFileFormats; i++)
	MakeWidget(ww, w, smeBSBObjectClass, setFileFormatMenuButton,
		   SoundFileFormatToString(i));

    /* data format menu */
    MakePopup(w, g->topLevel, simpleMenuWidgetClass, "dataFormatMenu");

    for (i = 0; i < AuServerNumFormats(g->aud); i++)
	MakeWidget(ww, w, smeBSBObjectClass, setDataFormatMenuButton,
		   AuFormatToString(AuServerFormat(g->aud, i)));

    makeBucketDialog(g);

    if (g->inputDeviceId)
    {
	makeRecordDialog(g);

	if (!(AuDeviceChangableMask(AuServerDevice(g->aud, g->inputDeviceNum)) &
	      AuCompDeviceLineModeMask))
	    XtVaSetValues(g->rec.mode, XtNsensitive, False, NULL);
    }
    else
    {
	g->rec.popShell = (Widget) 0;
	XtVaSetValues(g->record, XtNsensitive, False, NULL);
	XtVaSetValues(g->buf.record, XtNsensitive, False, NULL);
    }

}

#define SET_WIDTH(_w)							       \
{									       \
    XtVaGetValues(_w, XtNx, &x, NULL);					       \
    XtVaSetValues(_w, XtNwidth, maxX - x, NULL);			       \
}

static void
alignWidgets(GlobalDataPtr g)
{
    Position        maxX,
                    x;
    Dimension       width;
    BucketDialogDataPtr b = &g->buf;
    RecordDialogDataPtr r = &g->rec;
    SaveDialogDataPtr s = &g->save;

    /* main window */
    XtRealizeWidget(g->topLevel);
    XtVaGetValues(XtNameToWidget(g->form, "version"),
		  XtNx, &x, XtNwidth, &width, NULL);
    maxX = x + width - 2;		       /* the -2 is a hack since
					        * "version" has no border */

    SET_WIDTH(g->leftMeter);
    SET_WIDTH(g->rightMeter);
    SET_WIDTH(g->volumeBar);
    SET_WIDTH(g->info);
    SET_WIDTH(g->directory);
    SET_WIDTH(g->template);
    SET_WIDTH(g->viewport);

    /* buckets window */
    XtRealizeWidget(b->popShell);
    XtVaGetValues(b->accessMenuButton, XtNx, &x, XtNwidth, &width, NULL);
    maxX = x + width;

    SET_WIDTH(b->viewport);

    if (r->popShell)
    {
	/* record window */
	XtRealizeWidget(r->popShell);
	XtVaGetValues(r->readOnly, XtNx, &x, XtNwidth, &width, NULL);
	maxX = x + width;

	SET_WIDTH(r->file);
	SET_WIDTH(r->comment);
	SET_WIDTH(r->fileFormatMenuButton);
	SET_WIDTH(r->dataFormatMenuButton);
	SET_WIDTH(r->gainBar);

	XtVaSetValues(r->fileFormatMenuButton, XtNresizable, False, NULL);
	XtVaSetValues(r->dataFormatMenuButton, XtNresizable, False, NULL);
    }

    XtRealizeWidget(s->popShell);
    XtVaGetValues(s->file, XtNx, &x, XtNwidth, &width, NULL);
    maxX = x + width;
    SET_WIDTH(s->fileFormatMenuButton);
    XtVaSetValues(s->fileFormatMenuButton, XtNresizable, False, NULL);

    XtVaSetValues(g->viewport, XtNresizable, False, NULL);
    XtVaSetValues(b->viewport, XtNresizable, False, NULL);
}

int
main(int argc, char **argv)
{
    int             i,
                    endian = 1;
    char           *audioServerString = NULL,
                   *dir = NULL;
    GlobalDataRec   globalData,
                   *globals;
    XtAppContext    appContext;
    extern int      AuMonitorFormat;
    static XtActionsRec Actions[] = {{"ok", okAction}};

    globals = &globalData;

    globals->topLevel = XtVaAppInitialize(&appContext, APP_CLASS, NULL, ZERO,
					  &argc, argv, defaultResources,
					  NULL, 0);

    globals->volume = DEFAULT_VOLUME;
    globals->numFiles = 0;
    globals->dpy = XtDisplay(globals->topLevel);

    while (argc > 1)
    {
	argv++;
	argc--;

	if (!strcmp("-a", *argv) || !strcmp("-audio", *argv))
	{
	    audioServerString = argv[1];
	    argv++;
	    argc--;
	    if (!argc)
	    {
		printf("usage: audemo [ -toolkitoption ...] [-audio AUDIOSERVER] [directory]\n");
		exit(1);
	    }
	}
	else
	    dir = *argv;
    }

    if (!dir)
	dir = (char *) getcwd(NULL, 256);

    if (!(globals->aud =
	  AuOpenServer(audioServerString, 0, NULL, 0, NULL, NULL)))
	fatalError("Can't connect to audio server", NULL);

    globals->inputDeviceId = (AuDeviceID) 0;

    for (i = 0; i < AuServerNumDevices(globals->aud); i++)
	if ((AuDeviceKind(AuServerDevice(globals->aud, i)) ==
	     AuComponentKindPhysicalInput))
	{
	    globals->inputDeviceId =
		AuDeviceIdentifier(AuServerDevice(globals->aud, i));
	    globals->inputDeviceNum = i;
	    break;
	}

    XtAppAddActions(appContext, Actions, XtNumber(Actions));

    createWidgets(globals, dir);
    rescanCB(NULL, globals, NULL);
    alignWidgets(globals);

    AuMonitorFormat = *(char *) &endian ? AuFormatLinearSigned16LSB :
	AuFormatLinearSigned16MSB;

    AuXtAppAddAudioHandler(appContext, globals->aud);
    XtAppMainLoop(appContext);
    return 0;
}

#endif						/* XT */

static void
fatalError(const char *message, const char *arg)
{
    fprintf(stderr, message, arg);
    fprintf(stderr, "\n");
    exit(1);
}

static int
sortRoutine(const void *ap, const void *bp)
{
    const char **a = (const char **)ap;
    const char **b = (const char **)bp;

    return strcmp(strrchr(*a, '/') + 1, strrchr(*b, '/') + 1);
}

static char   **
makeFileList(char **fileNames, int nfiles)
{
    char          **fileList,
                   *p;
    int             i;

    qsort(fileNames, nfiles, sizeof(char *), sortRoutine);

    if (!(fileList = (char **) malloc(sizeof(char *) * nfiles)))
	fatalError("Can't malloc file list in makeFileList", NULL);

    for (i = 0; i < nfiles; i++)
    {
      if (p = strrchr(fileNames[i], '/')) 
        p++;
      else
        p = fileNames[i];
      
	fileList[i] = (char *) strdup(p);

	if ((p = strrchr(fileList[i], '.')))
	    *p = 0;

	fileList[i] = (char *) realloc(fileList[i], strlen(fileList[i]) + 1);

	if (!fileList[i])
	    fatalError("Can't realloc file list in makeFileList", NULL);
    }

    return fileList;
}

#ifndef WIN32
static FILE    *
startFind(char *dir, char *template)
{
    char           *cmd,
                   *p;
    FILE           *fp;
    int             first = 1;

    cmd = (char *) malloc(2000);

    sprintf(cmd, "find %s -type f -a \\( ", dir);

    p = strtok(template, " ");

    while (p)
    {
	if (!first)
	    strcat(cmd, " -o -name '");
	else
	{
	    strcat(cmd, " -name '");
	    first = 0;
	}

	strcat(cmd, p);
	strcat(cmd, "'");

	p = strtok(NULL, " ");
    }

    strcat(cmd, " \\) -print");

    fp = popen(cmd, "r");
    free(cmd);
    return fp;
}
#endif /* !WIN32 */

static int
getFileNames(char *dir, char ***fileNames, char *template)
{
#ifndef WIN32
    int             files;
    FILE           *fp;
    char            line[BUF_SIZE];

    /* prime the realloc pump */
    *fileNames = (char **) malloc(1);

    if (!fileNames)
	fatalError("Can't malloc file names in getFileNames", NULL);

    if (!(fp = startFind(dir, template)))
    {
	free(*fileNames);
	return 0;
    }

    files = 0;

    while (fgets(line, BUF_SIZE, fp))
    {
	*fileNames =
	    (char **) realloc(*fileNames, sizeof(char *) * (files + 1));

	if (!*fileNames)
	    fatalError("Can't realloc file names in getFileNames", NULL);

	line[strlen(line) - 1] = 0;	       /* zap the trailing newline */
	(*fileNames)[files++] = (char *) strdup(line);
    }

    pclose(fp);

    if (!files)
	free(*fileNames);

    return files;
#else /* WIN32 */
    WIN32_FIND_DATA fileInfo;
    char *d;
    int files = 0;
    HANDLE h;

    *fileNames = NULL;
    if (!(d = (char *) malloc(strlen(dir) + 5)))
	return 0;

    strcpy(d, dir);
    strcat(d, "/*.*");
    h = FindFirstFile(d, &fileInfo);
    free(d);

    if (!h)
	return 0;

    do
    {
	if (!(fileInfo.dwFileAttributes &
	      (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM)))
	{
	    char *p = NULL;

	    if (p = (char *) malloc(strlen(dir) + strlen(fileInfo.cFileName) +
				    2))
	    {
		*fileNames = (char **) realloc(*fileNames, sizeof(char *) *
					       (files + 1));
		strcpy(p, dir);
		strcat(p, "/");
		strcat(p, fileInfo.cFileName);

		(*fileNames)[files++] = p;
	    }
	}
    } while(FindNextFile(h, &fileInfo));

    FindClose(h);
    return files;
#endif /* WIN32 */
}

#ifdef WIN32
#include <sys/timeb.h>

int
gettimeofday(struct timeval *tv, void *dummy)
{
    struct _timeb tm;
    _ftime(&tm);
    tv->tv_sec = tm.time;
    tv->tv_usec = tm.millitm;
    return 0;
}
#endif /* WIN32 */
