#include "snd.h"

#ifdef SGI
  #include <audio.h>
  #ifdef AL_RESOURCE
    #define NEW_SGI_AL 1
    #define OLD_SGI_AL 0
  #else
    #define NEW_SGI_AL 0
    #define OLD_SGI_AL 1
  #endif
#else
  #define NEW_SGI_AL 0
  #define OLD_SGI_AL 0
#endif

#define DEBUGGING 0
#define MIXER_SAVE_FILE ".snd-mixer"


/* TODO:
   multiple inputs in new sgi al on properly equipped machines (how do Indys behave in this case?)
   smoothed amp sliders(?)
   clicks upon swap or whatever
   on-going output sonogram (this is just an fft+running display)?
   CD input (does this actually make any sense?) (in Sun it's AUDIO_INTERNAL_CD_IN -suncs.txt)
   Sun audio tested
   sgi o2 aes untested
   on old al need to catch o2 case (i.e. stereo)
   matrix to simplify sliders?
   need default input/output device choice in snd-clm
   are gain controls backwards in O2 new AL mic?
   why does adat input cause snd to hang sometimes?
   multi-card linux line inputs??
   apparently there is a way to select dynamic/condensor mic (bias/boost) in OSS -- how?
   what is RECLEV in OSS? or MONITOR?
   on some SB's we could read dsp0, write dsp1 (get around lack of full-duplex, maybe -- use AUX_OUTPUT_DEVICE for this)
   4-channel non-clock-synchronous out in OSS with Ensoniq via line-in connector
   currently mic in Ensoniq is very noisy
   */

#define MAX_OUT_CHANS 8
#define MAX_SOUNDCARDS 8

#if HAVE_OSS
void read_mixer_state(char *file);
void write_mixer_state(char *file);
#endif


typedef struct {
  Widget meter;
  Widget max_button;
  Display *dp;
  Drawable wn;
  GC gc;
  int scr;
  int on_off;
  int clipped;
  float current_val,last_val;
  float max_val;
  float red_deg;
  float size;
  int light_x, light_y, center_x, center_y;
  snd_state *ss;
  Pixmap off_label;
  Pixmap on_label;
  Pixmap clip_label;
} VU;

typedef struct {
  Widget label, number, slider;
  snd_state *ss;
  int last_amp,type,in,out;
  int device_in_chan;
} AMP;

typedef struct {
  int in_chans,out_chans,meters_size,amps_size,active_size,on_buttons_size,in_chan_loc;
  VU **meters;         /* has meter widget, can assume frame is local to creator */
  AMP **amps;          /* chans -- has label number slider amp which_amp -- software input gains to output */ 
  int *active;         /* which (incoming or file_out outgoing) channels are actively shoveling samples */
  snd_state *ss;
  Widget *on_buttons;
  Widget reset_button;
  Widget pane;
  Dimension pane_size;
  int device,system;          /* audio.c style device descriptor */
} PANE;

typedef struct {
  snd_state *ss;
  int chan,field,device,system;
  int gain;
  PANE *p;
  Widget wg;
} Wdesc;



#define SMALL_FONT_CUTOFF .85
#define SMALLER_FONT_CUTOFF .7
#define SMALL_FONT "6x10"
#define SMALLER_FONT "5x7"

static char timbuf[64];
static char msgbuf[512];

static int systems = 1;             /* multi-card setups */
static int audio_open = 0;          /* input active */
static int monitor_open = 0;        /* speakers active */
static int record_fd[MAX_SOUNDCARDS]; /* input (audio hardware) channel */
static int monitor_fd;              /* output (file) channel */

static XtWorkProcId ever_read = 0;

static int in_datum_size = 2;
static int output_fd = -1;
static int audio_out_chans = 2,out_datum_size = 2,out_type;

/* on the SGI 1024 is very prone to clicks */
static short *record_buf[MAX_SOUNDCARDS];
static float *fbuf = NULL;          /* interleaved input as floats */
static float *ffbuf = NULL; 
static short *monitor_buf = NULL;   /* speaker (scaled) output */
static short *out_buf = NULL;       /* file (unscaled) output (used directly if possible) */
static int **obufs = NULL;          /* formatted non-interleaved output */
static int data_is_compatible = 1;  /* does output need to be reformatted */
static int total_out_samps,duration_samps;
static int overall_in_chans;
static int input_channels[MAX_SOUNDCARDS];

static int *rec_in_active = NULL;   /* overall_in_chans */
static int *rec_out_active = NULL;  /* (file)_out_chans */
static VU **rec_in_VU = NULL;       /* from rec in to associated meter */
static VU **rec_out_VU = NULL;      /* from rec out to associated meter */
static float *outvals = NULL;       /* out_chans */
static float *in_max = NULL;        /* overall_in_chans (for block-oriented VU meter update) */
static float *out_max = NULL;       /* same on output */
static int *in_device_on = NULL;    /* is this input channel receiving input */
static int *in_device_chan = NULL;  /* which actual (audio) input channel is associated with which (virtual) recorder device */

static float **rec_in_amps = NULL;  /* overall_in_chans X out_chans */
static float *rec_out_amps = NULL;  /* out_chans (independent of file write: monitor vol) */
static float *audio_gains = NULL;   /* audio gain values (widgets are per pane) */
static Wdesc **audio_GAINS = NULL;  /* associated sliders and descriptors for write audio state */
static int audio_gains_size = 0;
static AMP ***rec_in_AMPS = NULL;
static AMP **rec_out_AMPS = NULL;

static Widget recorder = NULL;      /* the outer dialog shell */
static int recording = 0;

static PANE **all_panes = NULL;
static int all_panes_size = 0;
static int out_file_pane;
static int autoload_button = 0;
static int digital_in_button = 0;
static int microphone_button = 0;
static int line_in_button = 0;

static Widget header_list,format_list,srate_text,chans_text,rec_size_text;
static Widget file_duration,comment_text,messages,record_button,reset_button,file_text;
static Widget *device_buttons;
static int device_buttons_size = 0;
#if NEW_SGI_AL
  static int active_device_button = -1;
#endif
static int gain_ctr = 0;
static int overall_input_ctr = 0;
#if HAVE_OSS
  static int mixer_gains_posted[MAX_SOUNDCARDS];
  static int tone_controls_posted[MAX_SOUNDCARDS];
  static int settings_saved = 0;
#endif
static XmFontList small_fontlist;

static vu_label **vu_labels = NULL;
static int vu_labels_size = 0;
static int current_vu_label = 0;

static char **pending_error = NULL;
static int pending_errors = 0;
static int pending_error_size = 0;

static void set_read_in_progress (snd_state *ss);

static float get_audio_gain(Wdesc *wd)
{
  /* read and set local audio_gains value as well (for snd-clm connection) */
  float g[1];
  read_audio_state(AUDIO_SYSTEM(wd->system) | (wd->device),wd->field,wd->chan,g);
  audio_gains[wd->gain]=g[0];
  return(g[0]);
}

static void set_audio_gain(Wdesc *wd, float amp) 
{
  float g[1];
  g[0] = amp;
  if (wd->device == DAC_FILTER_DEVICE) /* bass or treble control affects both channels at once */
    {
      write_audio_state(AUDIO_SYSTEM(wd->system) | (wd->device),wd->field,0,g);
      write_audio_state(AUDIO_SYSTEM(wd->system) | (wd->device),wd->field,1,g);
    }
  else 
    write_audio_state(AUDIO_SYSTEM(wd->system) | (wd->device),wd->field,wd->chan,g);
  audio_gains[wd->gain] = amp;
}

static int snd_close_audio(int val) 
{
  int i;
  if (audio_open)
    {
      for (i=0;i<systems;i++)
	if (record_fd[i] != 1)
	  close_audio(record_fd[i]);
      audio_open = 0;
    }
  if (ever_read) 
    {
      XtRemoveWorkProc(ever_read);
      ever_read = 0;
    }
  if (monitor_open)
    {
      close_audio(monitor_fd);
      monitor_open = 0;
    }
  return(val);
}

static void record_report(Widget text, ...)
{
  /* place time-stamped message in text window */
  time_t ts;
  int pos;
  va_list ap;
  char *nextstr;
  int textpos = 0;
#if (!defined(BEOS)) && (!defined(HAVE_CONFIG_H)) || defined(HAVE_STRFTIME)
  time(&ts);
  strftime(timbuf,64,"%H:%M:%S",localtime(&ts));
  sprintf(msgbuf,"\n[%s] ",timbuf);
#endif
  pos = XmTextGetLastPosition(text);
  if (pos == 0) 
    XmTextSetString(text,msgbuf);
  else XmTextInsert(text,pos,msgbuf);
  va_start(ap,text);
  while ((nextstr = va_arg(ap,char *)))
    {
      textpos = XmTextGetLastPosition(text);
      XmTextInsert(text,textpos,nextstr);
    }
  XmTextShowPosition(text,XmTextGetLastPosition(text));
  va_end(ap);
}

static int fire_up_recorder(snd_state *ss);

#if OLD_SGI_AL
static void set_line_source(snd_state *ss, int in_digital)
{
  int aud,err;
  aud = audio_open;
  if (aud) snd_close_audio(0);
  input_channels[0] = ((in_digital) ? 2 : 4);
  audio_out_chans = input_channels[0];
  err = write_audio_state(DEFAULT_DEVICE,DEVICE_FIELD,((in_digital) ? DIGITAL_IN_DEVICE : MICROPHONE_DEVICE),NULL);
  if (err == -1) 
    {
      fprintf(stderr,"err: %s",audio_error_name(audio_error()));
      if (recorder) record_report(messages,"set input source: ",audio_error_name(audio_error()),NULL);
    }
  in_device_on[0] = (!in_digital); 
  in_device_on[1] = (!in_digital);
  in_device_on[2] = (!in_digital); 
  in_device_on[3] = (!in_digital);
  in_device_on[4] = in_digital; 
  in_device_on[5] = in_digital;
  if (aud) fire_up_recorder(ss);
}
#endif

static void set_audio_srate(snd_state *ss, int device, int srate, int system)
{
  float g[1];
  int aud;
#if (!NEW_SGI_AL)
  aud = audio_open;
  if (aud) snd_close_audio(0);
  g[0] = (float)srate;
  write_audio_state(AUDIO_SYSTEM(system) | device,SRATE_FIELD,0,g);
  if (aud) fire_up_recorder(ss);
#endif
}

#if 0
static int full_duplex(int system)
{
  float val[1];
  read_audio_state(AUDIO_SYSTEM(system) | READ_WRITE_DEVICE,CHANNEL_FIELD,0,val);
  return((int)(val[0]));
}
#endif

void attach_error(char *msg)
{
  /* need to report errors that crop up during recorder initialization (when the message window is unready) */
  if (pending_errors == pending_error_size)
    {
      pending_error_size += 8;
      if (pending_errors == 0)
	pending_error = (char **)calloc(pending_error_size,sizeof(char *));
      else pending_error = (char **)realloc(pending_error,pending_error_size * sizeof(char *));
    }
  pending_error[pending_errors] = copy_string(msg);
  pending_errors++;
}


/* -------------------------------- ROTATE TEXT -------------------------------- */
/* rotate and scale text */

static float degrees_to_radians(float x) {return(x * 3.14159265 / 180.0);}

static Pixmap transform_text (Widget w, char *str, XFontStruct *font, float angle_in_degrees, 
			      float xscl, float yscl, int *nw, int *nh, Pixel bg, Pixel fg)
{
  /* scale str in font font by xscl and yscl, then rotate clockwise by angle_in_degrees degrees */
  /* (i.e. 45 points text south-east), new bounding box (text centered in it) returned in nw and nh */
  /* returned pixmap must be freed (if at all) by the caller */
  /* bg = background color, fg = foreground (text) color) */
  float matrix[4];
  float angle_in_radians;
  XImage *before,*after;
  Pixmap pix,rotpix;
  unsigned int width,height,depth,nwidth,nheight,x,y,nx,ny,tx,ty;
  int inx,iny;
  GC erase_gc,draw_gc;
  XGCValues v;
  char *data;
  unsigned long px;
  Display *dp;
  Drawable wn;
  Visual *vis;
  int scr;
  int bx0,bx1,by0,by1,b;
  int i,j;
  if (str == NULL) return(0);
  /* set up transformation matrix */
  angle_in_radians = degrees_to_radians(angle_in_degrees);
  matrix[0] = cos(angle_in_radians) * xscl;
  matrix[1] = sin(angle_in_radians) * xscl;
  matrix[2] = -sin(angle_in_radians) * yscl;
  matrix[3] = cos(angle_in_radians) * yscl;
  
  /* get the usual X info */
  dp = XtDisplay(w);
  wn = XtWindow(w);
  scr = DefaultScreen(dp);
  vis = DefaultVisual(dp,scr);

  v.foreground = bg;
  v.background = bg;
  erase_gc = XtGetGC(w,GCForeground | GCBackground,&v);

  XtVaGetValues(w,XmNdepth,&depth,NULL);
  v.background = bg;
  v.foreground = fg;
  draw_gc = XtGetGC(w,GCForeground | GCBackground,&v);

  /* find extent of original text, expand out to byte boundaries */
  XSetFont(dp,draw_gc,font->fid);
  width = XTextWidth(font,str,strlen(str));
  height = (font->ascent+font->descent);
  if (width%8) width = 8*(1 + (int)(width/8));
  if (height%8) height = 8*(1+(int)(height/8));

  /* get bounding box of transformed text */
  bx0=0; bx1=0; by0=0; by1=0;
  b=width*matrix[0];
  if (b<0) bx0=b; else bx1=b;
  b=height*matrix[2];
  if (b<0) bx0+=b; else bx1+=b;
  b=width*matrix[1];
  if (b<0) by0=b; else by1=b;
  b=height*matrix[3];
  if (b<0) by0+=b; else by1+=b;
  
  /* set translation vector so we're centered in the resultant pixmap */
  if (bx0<0) tx=-bx0; else tx=0;
  if (by0<0) ty=-by0; else ty=0;
  nx = bx1-bx0;
  ny = by1-by0;

  /* expand result bounds to byte boundaries */
  if (nx%8) nwidth = 8*(1+(int)(nx/8)); else nwidth = nx;
  if (ny%8) nheight = 8*(1+(int)(ny/8)); else nheight = ny;
  (*nw) = nwidth;
  (*nh) = nheight;

  /* create pixmaps, fill with background color, write string to pix */
  pix = XCreatePixmap(dp,wn,width,height,depth);
  rotpix= XCreatePixmap(dp,wn,nwidth,nheight,depth);
  XFillRectangle(dp,pix,erase_gc,0,0,width,height);
  XFillRectangle(dp,rotpix,erase_gc,0,0,nwidth,nheight);
  XDrawImageString(dp,pix,draw_gc,0,height,str,strlen(str));

  /* dump pixmap bits into an "image", image data will be freed automatically later */
  data = (char *)calloc(width*height,sizeof(char));
  before = XCreateImage(dp,vis,depth,XYPixmap,0,data,width,height,8,0);
  XGetSubImage(dp,pix,0,0,width,height,AllPlanes,XYPixmap,before,0,0);
  data = (char *)calloc(nwidth*nheight,sizeof(char));
  after = XCreateImage(dp,vis,depth,XYPixmap,0,data,nwidth,nheight,8,0);

  /* clear background of result image */
  for (x=0;x<nwidth;x++) {for (y=0;y<nheight;y++) XPutPixel(after,x,y,bg);}

  /* write transformed pixels to result image */
  for (x=0;x<width;x++)
    {
      for (y=0;y<height;y++)
	{
	  px = XGetPixel(before,x,y);
	  if (px != bg)
	    {
	      bx0 = xscl; if (bx0 == 0) bx0=1;  /* draw full lines if possible (i.e. fill in scaled gaps) */
	      by0 = yscl; if (by0 == 0) by0=1;
	      for (i=0;i<bx0;i++)
		{
		  for (j=0;j<by0;j++)
		    {
		      inx = tx + round((x+(float)i/xscl)*matrix[0] + (y+(float)j/yscl)*matrix[2]);  if (inx<0) inx=0; if (inx>=nwidth) inx=nwidth-1;
		      iny = ty + round((x+(float)i/xscl)*matrix[1] + (y+(float)j/yscl)*matrix[3]);  if (iny<0) iny=0; if (iny>=nheight) iny=nheight-1;
		      XPutPixel(after,inx,iny,px);
		    }
		}
	    }
	}
    }

  /* dump image into result pixmap (needed for later display) */
  XPutImage(dp,rotpix,draw_gc,after,0,0,0,0,nwidth,nheight);

  /* cleanup */
  XDestroyImage(before);  /* frees data as well, or so claims the documentation */
  XDestroyImage(after);
  XFreePixmap(dp,pix);
  return(rotpix);
}

/* -------------------------------- ICONS -------------------------------- */

static unsigned char speaker_bits[] = {
   0x00, 0x07, 0xc0, 0x04, 0x30, 0x04, 0x0e, 0x04, 0x06, 0x04, 0x06, 0x04,
   0x06, 0x04, 0x06, 0x04, 0x0e, 0x04, 0x30, 0x04, 0xc0, 0x04, 0x00, 0x07};

static unsigned char mic_bits[] = {
   0xf0, 0x00, 0x58, 0x01, 0xa8, 0x01, 0xf8, 0x01, 0x08, 0x01, 0x0f, 0x0f,
   0x09, 0x09, 0x09, 0x09, 0xf9, 0x09, 0xf1, 0x08, 0x61, 0x08, 0xff, 0x0f};

static unsigned char line_in_bits[] = {
   0x00, 0x04, 0x40, 0x02, 0x20, 0x02, 0x24, 0x01, 0x12, 0x01, 0xff, 0x0f,
   0x12, 0x01, 0x24, 0x01, 0x20, 0x02, 0x40, 0x02, 0x00, 0x04, 0x00, 0x00};

static unsigned char aes_bits[] = {
   0xe6, 0x3d, 0x29, 0x04, 0x29, 0x04, 0x29, 0x04, 0xef, 0x3c, 0x29, 0x20,
   0x29, 0x20, 0x29, 0x20, 0xe9, 0x3d};

static unsigned char digin_bits[] = {
   0x03, 0x0c, 0x03, 0x0c, 0x03, 0x0c, 0xff, 0x0f, 0xff, 0x0f, 0xab, 0x0e,
   0x57, 0x0d, 0xff, 0x0f, 0xff, 0x0f, 0x03, 0x0c, 0x03, 0x0c, 0x03, 0x0c};

static unsigned char adat_bits[] = {
   0x40, 0x10, 0x40, 0x10, 0x40, 0x38, 0x77, 0x17, 0x55, 0x15, 0x55, 0x15,
   0x55, 0x15, 0x7f, 0x1f};

static Pixmap speaker_icon,line_in_icon,mic_icon,aes_icon,adat_icon,digital_in_icon;

static void make_record_icons(Widget w, snd_state *ss)
{
  XGCValues v;
  GC gc;
  int depth;
  XtVaGetValues(w,XmNforeground,&v.foreground,XmNbackground,&v.background,XmNdepth,&depth,NULL);
  gc = XtGetGC(w,GCForeground | GCBackground,&v);
  speaker_icon = make_pixmap(ss,speaker_bits,12,12,depth,gc);
  mic_icon = make_pixmap(ss,mic_bits,12,12,depth,gc);
  line_in_icon = make_pixmap(ss,line_in_bits,12,12,depth,gc);
  digital_in_icon = make_pixmap(ss,digin_bits,12,12,depth,gc);
  aes_icon = make_pixmap(ss,aes_bits,14,9,depth,gc);
  adat_icon = make_pixmap(ss,adat_bits,14,8,depth,gc);
}

static Pixmap device_icon(int device)
{
  switch (device)
    {
    case DIGITAL_OUT_DEVICE:
    case LINE_OUT_DEVICE:
    case DEFAULT_DEVICE:
    case DAC_OUT_DEVICE:
    case READ_WRITE_DEVICE:
    case SPEAKERS_DEVICE:   return(speaker_icon); break;
    case ADAT_OUT_DEVICE:
    case ADAT_IN_DEVICE:    return(adat_icon); break;
    case AES_OUT_DEVICE:  
    case AES_IN_DEVICE:     return(aes_icon); break;
    case LINE_IN_DEVICE:    return(line_in_icon); break;
    case DIGITAL_IN_DEVICE: return(digital_in_icon); break;
    case MICROPHONE_DEVICE: 
    default:                return(mic_icon); break;
    }

}


/* -------------------------------- VU METER -------------------------------- */

static XFontStruct *get_vu_font(snd_state *ss, float size)
{
  char font_name[64];
  int font_size;
  char *vu_font_name;
  XFontStruct *label_font;
  font_size = (int)(size*12*vu_font_size(ss));
  if (font_size < 5) font_size = 5;
  vu_font_name = vu_font(ss);
  if (!vu_font_name)
    {
      if (size < 0.75) 
#ifndef SGI
	vu_font_name = "fixed";
#else
        vu_font_name = "courier";
#endif
      else
	{
	  if (size < 1.25) 
	    vu_font_name = "courier";
	  else vu_font_name = "times";
	}
    }
  sprintf(font_name,"-*-%s-%s-r-*-*-%d-*-*-*-*-*-*",
	  vu_font_name,
	  (font_size > 10) ? "bold" : "*",
	  font_size);
  
  label_font = XLoadQueryFont(main_DISPLAY(ss),font_name);
  if (!(label_font))
    {
      sprintf(font_name,"-*-%s-*-*-*-*-%d-*-*-*-*-*-*",vu_font_name,font_size);
      label_font=XLoadQueryFont(main_DISPLAY(ss),font_name);
      if (!(label_font))
	{
	  sprintf(font_name,"-*-courier-*-*-*-*-%d-*-*-*-*-*-*",font_size);
	  label_font=XLoadQueryFont(main_DISPLAY(ss),font_name);
	  while (!(label_font))
	    {
	      sprintf(font_name,"-*-*-*-*-*-*-%d-*-*-*-*-*-*",font_size);
	      label_font=XLoadQueryFont(main_DISPLAY(ss),font_name);
	      font_size++;
	      if (font_size>60) {label_font=XLoadQueryFont(main_DISPLAY(ss),"-*-*-*-*-*-*-*-*-*-*-*-*-*"); break;}
	    }
	}
    }
  return(label_font);
}

#define VU_OFF 0
#define VU_ON 1
#define VU_CLIPPED 2
#define CLIPPED_TIME 10
#define CLIPPED_TRIGGER 0.99
#define VU_NEEDLE_SPEED 0.25
#define VU_BUBBLE_SPEED 0.025
#define VU_BUBBLE_SIZE (15*64)

#define LIGHT_X 120
#define LIGHT_Y 100
#define CENTER_X 120
#define CENTER_Y 160
#define VU_COLORS 11
#define VU_NUMBERS 15
/* these are for the highly optimized size=1.0 case */

/* major bug in earlier versions of X involving XImages causing all kinds of errors,
 * so we save these three images as Xpm data files, and read them back in in early
 * linuces -- this kludgery is not necessary in up-to-date versions of Linux.
 * The upshot is that old Linuces will die horribly if you use size != 1.0.
 */

static Pixel yellows[VU_COLORS];
static Pixel reds[VU_COLORS];
static int vu_colors_allocated = 0;
static int yellow_vals[] = {0,16,32,64,96,128,160,175,185,200,210,220,230,240};

#if HAVE_XPM
  int allocate_meter_2(Widget w, vu_label *vu);
#endif

static void allocate_meter_1(snd_state *ss, vu_label *vu)
{
  /* called only if size not allocated yet and size=1.0 not available as pre-made pixmap */
  int scr;
  Display *dp;
  Drawable wn;
  Colormap cmap;
  XColor tmp_color;
  int i,j,k;
  Pixel white,black,red;
  unsigned int depth;
  int band;
  XPoint pts[16];
  int x0,y0,x1,y1;
  float rdeg;
  XGCValues gv;
  GC gc;
  float size;
  Pixmap numbers[VU_NUMBERS];
  int wids[VU_NUMBERS],hgts[VU_NUMBERS];

  float BAND_X;
  float BAND_Y;

  size = vu->size;
  BAND_X = 2.75*size;
  BAND_Y = 3.25*size;
  red = (ss->sgx)->red;
  dp = XtDisplay(recorder);
  wn = XtWindow(recorder);
  scr = DefaultScreen(dp);
  cmap = DefaultColormap(dp,scr);
  black = BlackPixel(dp,scr);
  white = WhitePixel(dp,scr);
  XtVaGetValues(recorder,XmNdepth,&depth,NULL);
  if (!vu_colors_allocated)
    {
      vu_colors_allocated = 1;
      tmp_color.flags = DoRed | DoGreen | DoBlue;
      tmp_color.red = (unsigned short)65535;
      for (i=0;i<VU_COLORS;i++)
	{
	  tmp_color.blue = (unsigned short)(256*yellow_vals[i]);
	  tmp_color.green = (unsigned short)(256*230 + 26*yellow_vals[i]);
	  XAllocColor(dp,cmap,&tmp_color);
	  yellows[i] = tmp_color.pixel;
	}
      for (i=0;i<VU_COLORS;i++)
	{
	  tmp_color.blue = (unsigned short)(128*yellow_vals[i]);
	  tmp_color.green = (unsigned short)(128*yellow_vals[i]);
	  XAllocColor(dp,cmap,&tmp_color);
	  reds[i] = tmp_color.pixel;
	}
    }
  /* need two versions of these, one with white bg and black fg for "off" state, then... */
  j=0;
  numbers[j] = transform_text(recorder,"0.0",vu->label_font,-40.0,1.0,1.0,&wids[j],&hgts[j],yellows[9],red); j++;
  numbers[j] = transform_text(recorder,"0.25",vu->label_font,-25.0,1.0,1.0,&wids[j],&hgts[j],yellows[7],red); j++;
  numbers[j] = transform_text(recorder,"0.5",vu->label_font,0.0,1.0,1.0,&wids[j],&hgts[j],yellows[7],red); j++;
  numbers[j] = transform_text(recorder,"0.75",vu->label_font,23.0,1.0,1.0,&wids[j],&hgts[j],yellows[7],red); j++;
  numbers[j] = transform_text(recorder,"1.0",vu->label_font,40.0,1.0,1.0,&wids[j],&hgts[j],yellows[9],red); j++;
  numbers[j] = transform_text(recorder,"0.0",vu->label_font,-40.0,1.0,1.0,&wids[j],&hgts[j],white,black); j++;
  numbers[j] = transform_text(recorder,"0.25",vu->label_font,-25.0,1.0,1.0,&wids[j],&hgts[j],white,black); j++;
  numbers[j] = transform_text(recorder,"0.5",vu->label_font,0.0,1.0,1.0,&wids[j],&hgts[j],white,black); j++;
  numbers[j] = transform_text(recorder,"0.75",vu->label_font,23.0,1.0,1.0,&wids[j],&hgts[j],white,black); j++;
  numbers[j] = transform_text(recorder,"1.0",vu->label_font,40.0,1.0,1.0,&wids[j],&hgts[j],white,black); j++;
  numbers[j] = transform_text(recorder,"0.0",vu->label_font,-40.0,1.0,1.0,&wids[j],&hgts[j],reds[9],black); j++;
  numbers[j] = transform_text(recorder,"0.25",vu->label_font,-25.0,1.0,1.0,&wids[j],&hgts[j],reds[7],black); j++;
  numbers[j] = transform_text(recorder,"0.5",vu->label_font,0.0,1.0,1.0,&wids[j],&hgts[j],reds[7],black); j++;
  numbers[j] = transform_text(recorder,"0.75",vu->label_font,23.0,1.0,1.0,&wids[j],&hgts[j],reds[7],black); j++;
  numbers[j] = transform_text(recorder,"1.0",vu->label_font,40.0,1.0,1.0,&wids[j],&hgts[j],reds[9],black); j++;
      
  XtVaGetValues(recorder,XmNforeground, &gv.foreground,XmNbackground, &gv.background,NULL);
  gc = XtGetGC(recorder, GCForeground | GCBackground, &gv);

  for (k=0;k<2;k++) 
    {
      band = 1;
      if (k == 1)
	{
	  vu->clip_label = XCreatePixmap(dp,wn,CENTER_X*2*size,CENTER_Y*size,depth);
	  XSetForeground(dp,gc,reds[0]);	    
	  XFillRectangle(dp,vu->clip_label,gc,0,0,CENTER_X*2*size,CENTER_Y*size);
	}
      else 
	{
	  vu->on_label = XCreatePixmap(dp,wn,CENTER_X*2*size,CENTER_Y*size,depth);
	  XSetForeground(dp,gc,yellows[2]);
	  XFillRectangle(dp,vu->on_label,gc,0,0,CENTER_X*2*size,CENTER_Y*size);
	}
      /* initialize the sequence of nested polygons */
      pts[0].x = LIGHT_X*size - BAND_X;
      pts[0].y = LIGHT_Y*size;
      pts[1].x = pts[0].x;
      pts[1].y = pts[0].y - BAND_Y + 1;
      pts[2].x = pts[1].x + 1;
      pts[2].y = pts[1].y - 1;
      pts[3].x = pts[2].x + BAND_X * 2 - 2;
      pts[3].y = pts[2].y;
      pts[4].x = pts[3].x + 2;
      pts[4].y = pts[3].y + 1;
      pts[5].x = pts[4].x;
      pts[5].y = pts[0].y;
      pts[6].x = pts[0].x;
      pts[6].y = pts[0].y;
      if (k == 1)
	XFillPolygon(dp,vu->clip_label,gc,pts,7,Convex,CoordModeOrigin);
      else XFillPolygon(dp,vu->on_label,gc,pts,7,Convex,CoordModeOrigin);

      for (i=1;i<VU_COLORS;i++)
	{
	  band += i;
	  if (k == 1) 
	    XSetForeground(dp,gc,reds[i]); 
	  else 
	    {
	      if (i<2) XSetForeground(dp,gc,yellows[2]); else XSetForeground(dp,gc,yellows[i]);
	      /* why do the first two elements of this array return gray??? */
	    }
	  pts[6].x = LIGHT_X*size + band*BAND_X;
	  pts[6].y = pts[5].y;
	  pts[7].x = pts[6].x;
	  pts[7].y = LIGHT_Y*size - band*(BAND_Y - 1);
	  pts[8].x = LIGHT_X*size + band*(BAND_X - 1);
	  pts[8].y = LIGHT_Y*size - band*BAND_Y;
	  pts[9].x = LIGHT_X*size - band*(BAND_X - 1);
	  pts[9].y = pts[8].y;
	  pts[10].x = LIGHT_X*size - band*BAND_X;
	  pts[10].y = LIGHT_Y*size - band*(BAND_Y - 1);
	  pts[11].x = pts[10].x;
	  pts[11].y = pts[6].y;
	  pts[12].x = pts[0].x;
	  pts[12].y = pts[0].y;
	  if (k == 1)
	    XFillPolygon(dp,vu->clip_label,gc,pts,13,Complex,CoordModeOrigin);
	  else XFillPolygon(dp,vu->on_label,gc,pts,13,Complex,CoordModeOrigin);
	  for (j=0;j<6;j++) 
	    { 
	      /* set up initial portion of next polygon */
	      pts[j].x = pts[j+6].x;
	      pts[j].y = pts[j+6].y;
	    }
	}
    }

  vu->off_label = XCreatePixmap(dp,wn,CENTER_X*2*size,CENTER_Y*size,depth);
  /* not on, so just display a white background */
  XSetForeground(dp,gc,white);
  XFillRectangle(dp,vu->off_label,gc,0,0,CENTER_X*2*size,CENTER_Y*size);

  XSetForeground(dp,gc,black);
  /* draw the numbers */
  j=0;
  XCopyArea(dp,numbers[j],vu->on_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 114),size*(CENTER_Y - 116)); j++;
  XCopyArea(dp,numbers[j],vu->on_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 71), size*(CENTER_Y - 147)); j++;
  XCopyArea(dp,numbers[j],vu->on_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 11), size*(CENTER_Y - 153)); j++;
  XCopyArea(dp,numbers[j],vu->on_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X + 42), size*(CENTER_Y - 145)); j++;
  XCopyArea(dp,numbers[j],vu->on_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X + 88), size*(CENTER_Y - 116)); j++;
  
  j=5;
  XCopyArea(dp,numbers[j],vu->off_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 114),size*(CENTER_Y - 116)); j++;
  XCopyArea(dp,numbers[j],vu->off_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 71), size*(CENTER_Y - 147)); j++;
  XCopyArea(dp,numbers[j],vu->off_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 11), size*(CENTER_Y - 153)); j++;
  XCopyArea(dp,numbers[j],vu->off_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X + 42), size*(CENTER_Y - 145)); j++;
  XCopyArea(dp,numbers[j],vu->off_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X + 88), size*(CENTER_Y - 116));

  j=10;
  XCopyArea(dp,numbers[j],vu->clip_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 114),size*(CENTER_Y - 116)); j++;
  XCopyArea(dp,numbers[j],vu->clip_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 71), size*(CENTER_Y - 147)); j++;
  XCopyArea(dp,numbers[j],vu->clip_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X - 11), size*(CENTER_Y - 153)); j++;
  XCopyArea(dp,numbers[j],vu->clip_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X + 42), size*(CENTER_Y - 145)); j++;
  XCopyArea(dp,numbers[j],vu->clip_label,gc,0,0,wids[j],hgts[j],size*(CENTER_X + 88), size*(CENTER_Y - 116));

  /* draw the arcs */
  XDrawArc(dp,vu->on_label,gc,size*(CENTER_X - 120),size*(CENTER_Y - 120),size*(240),size*(240),45*64,90*64);
  XDrawArc(dp,vu->on_label,gc,size*(CENTER_X - 119),size*(CENTER_Y - 120),size*(239),size*(239),45*64,89*64);
  XDrawArc(dp,vu->on_label,gc,size*(CENTER_X - 119),size*(CENTER_Y - 119),size*(239),size*(239),45*64,89*64);
  XDrawArc(dp,vu->on_label,gc,size*(CENTER_X - 116),size*(CENTER_Y - 116),size*(232),size*(232),45*64,90*64);

  XDrawArc(dp,vu->off_label,gc,size*(CENTER_X - 120),size*(CENTER_Y - 120),size*(240),size*(240),45*64,90*64);
  XDrawArc(dp,vu->off_label,gc,size*(CENTER_X - 119),size*(CENTER_Y - 120),size*(239),size*(239),45*64,89*64);
  XDrawArc(dp,vu->off_label,gc,size*(CENTER_X - 119),size*(CENTER_Y - 119),size*(239),size*(239),45*64,89*64);
  XDrawArc(dp,vu->off_label,gc,size*(CENTER_X - 116),size*(CENTER_Y - 116),size*(232),size*(232),45*64,90*64);

  XDrawArc(dp,vu->clip_label,gc,size*(CENTER_X - 120),size*(CENTER_Y - 120),size*(240),size*(240),45*64,90*64);
  XDrawArc(dp,vu->clip_label,gc,size*(CENTER_X - 119),size*(CENTER_Y - 120),size*(239),size*(239),45*64,89*64);
  XDrawArc(dp,vu->clip_label,gc,size*(CENTER_X - 119),size*(CENTER_Y - 119),size*(239),size*(239),45*64,89*64);
  XDrawArc(dp,vu->clip_label,gc,size*(CENTER_X - 116),size*(CENTER_Y - 116),size*(232),size*(232),45*64,90*64);

  /* draw the axis ticks */
  for (i=0;i<5;i++)
    {
      rdeg = degrees_to_radians(45-i*22.5);
      x0 = CENTER_X*size+120*size*sin(rdeg);
      y0 = CENTER_Y*size-120*size*cos(rdeg);
      x1 = CENTER_X*size+130*size*sin(rdeg);
      y1 = CENTER_Y*size-130*size*cos(rdeg);
      XDrawLine(dp,vu->on_label,gc,x0,y0,x1,y1);
      XDrawLine(dp,vu->on_label,gc,x0+1,y0,x1+1,y1);
      XDrawLine(dp,vu->off_label,gc,x0,y0,x1,y1);
      XDrawLine(dp,vu->off_label,gc,x0+1,y0,x1+1,y1);
      XDrawLine(dp,vu->clip_label,gc,x0,y0,x1,y1);
      XDrawLine(dp,vu->clip_label,gc,x0+1,y0,x1+1,y1);
      if (i<4)
	{
	  for (j=1;j<6;j++)
	    {
	      rdeg = degrees_to_radians(45-i*22.5-j*(90.0/20.0));
	      x0 = CENTER_X*size+120*size*sin(rdeg);
	      y0 = CENTER_Y*size-120*size*cos(rdeg);
	      x1 = CENTER_X*size+126*size*sin(rdeg);
	      y1 = CENTER_Y*size-126*size*cos(rdeg);
	      XDrawLine(dp,vu->on_label,gc,x0,y0,x1,y1);
	      XDrawLine(dp,vu->off_label,gc,x0,y0,x1,y1);
	      XDrawLine(dp,vu->clip_label,gc,x0,y0,x1,y1);
	    }
	}
    }
  for (i=0;i<VU_NUMBERS;i++) XFreePixmap(dp,numbers[i]);
}

#if 0
  /* here's how the label xpm files were created */
  /* the data was then transferred to this file and cleaned up by hand */
  XpmWriteFileFromPixmap(dp,"onlabel.xpm",vu->on_label,NULL,NULL);  
  XpmWriteFileFromPixmap(dp,"offlabel.xpm",vu->off_label,NULL,NULL);  
  XpmWriteFileFromPixmap(dp,"cliplabel.xpm",vu->clip_label,NULL,NULL);
#endif

#if HAVE_XPM
  #include <X11/xpm.h>
#endif

static void allocate_meter(snd_state *ss, vu_label *vu)
{
#if HAVE_XPM  
  int err = XpmSuccess;
  if ((vu->size == 1.0) || (vu->size > 4.0) || (vu->size < .25))
    err = allocate_meter_2(recorder,vu);
  else allocate_meter_1(ss,vu);
  if (err != XpmSuccess) {vu->on_label = 0; vu->off_label = 0; vu->clip_label = 0;}
#else
  allocate_meter_1(ss,vu);
#endif
}

static vu_label *get_vu_label(snd_state *ss, float size)
{
  int i;
  vu_label *vu;
  for (i=0;i<current_vu_label;i++)
    {
      if (vu_labels[i]->size == size) return(vu_labels[i]);
    }
  if (current_vu_label >= vu_labels_size)
    {
      vu_labels_size += 8;
      if (!vu_labels)
	vu_labels = (vu_label **)calloc(vu_labels_size,sizeof(vu_label *));
      else vu_labels = (vu_label **)realloc(vu_labels,vu_labels_size * sizeof(vu_label *));
    }
  vu_labels[current_vu_label] = (vu_label *)calloc(1,sizeof(vu_label));
  vu = vu_labels[current_vu_label];
  vu->label_font = get_vu_font(ss,size);
  vu->size = size;
  current_vu_label++;
  allocate_meter(ss,vu);
  return(vu);
}

static void display_vu_meter(VU *vu)
{
  float deg,rdeg,val;
  int nx0,nx1,ny0,ny1,redx,redy,bub0,bub1,i,j;
  Pixmap label;
  snd_state *ss;
  float size;
  ss = vu->ss;
  size = vu->size;
  if (vu->current_val > CLIPPED_TRIGGER) 
    {
      if (vu->on_off == VU_ON) 
	{
	  vu->on_off = VU_CLIPPED;
	  vu->clipped = CLIPPED_TIME * ((float)(recorder_srate(ss)) / 22050.0);
	  /* might also change with record buffer size (recorder_buffer_size below)
	   * at 4096, we're getting updated here every 1024 samps (4 chans in always)
	   * which is fast enough to look smooth, except perhaps at 8kHz?
	   * so clipped_time == 10 gives us about a .5 sec flash
	   */
	}
    }
  if (vu->current_val > 1.0) vu->current_val = 1.0;
  switch (vu->on_off)
    {
    case VU_OFF: label = vu->off_label; break;
    case VU_CLIPPED: 
      vu->clipped--; 
      if (vu->clipped <= 0) 
	{
	  label = vu->on_label; 
	  vu->on_off = VU_ON;
	} 
      else label = vu->clip_label; 
      break;
    case VU_ON: label = vu->on_label; break;
    }
  if (label) XCopyArea(vu->dp,label,vu->wn,vu->gc,0,0,vu->center_x*2,vu->center_y,0,0);
  val = vu->current_val*VU_NEEDLE_SPEED + (vu->last_val*(1.0-VU_NEEDLE_SPEED));
  vu->last_val = val;
  deg = -45.0 + val*90.0;
  /* if (deg < -45.0) deg = -45.0; else if (deg > 45.0) deg = 45.0; */
  rdeg = degrees_to_radians(deg);
  nx0 = vu->center_x - (int)((float)(vu->center_y - vu->light_y) / tan(degrees_to_radians(deg+90)));
  ny0 = vu->light_y;
  nx1 = vu->center_x + 130*size*sin(rdeg);
  ny1 = vu->center_y - 130*size*cos(rdeg);
  XSetForeground(vu->dp,vu->gc,(ss->sgx)->black);
  XDrawLine(vu->dp,vu->wn,vu->gc,nx0,ny0,nx1,ny1);

  /* this shadow doesn't do much (and if +/-3 for depth, it looks kinda dumb) */
  if (deg != 0.0)
    {
      if (vu->on_off == VU_OFF)
	XSetForeground(vu->dp,vu->gc,(ss->sgx)->scale);
      else
	if (vu->on_off == VU_CLIPPED)
	  XSetForeground(vu->dp,vu->gc,(ss->sgx)->text);
	else XSetForeground(vu->dp,vu->gc,(ss->sgx)->white);
      if (deg < 0.0)
	XDrawLine(vu->dp,vu->wn,vu->gc,nx0-1,ny0,nx1-1,ny1);
      else XDrawLine(vu->dp,vu->wn,vu->gc,nx0+1,ny0,nx1+1,ny1);
      XSetForeground(vu->dp,vu->gc,(ss->sgx)->black);
    }

  if (vu->on_off != VU_OFF)
    {
      if (vu->current_val > vu->red_deg) 
	vu->red_deg = vu->current_val;
      else vu->red_deg = vu->current_val*VU_BUBBLE_SPEED + (vu->red_deg*(1.0 - VU_BUBBLE_SPEED));
      XSetForeground(vu->dp,vu->gc,(ss->sgx)->red);
      redx = vu->red_deg * 90*64;
      if (redx<(VU_BUBBLE_SIZE)) redy=redx; else redy=VU_BUBBLE_SIZE;
      bub0 = size*117;
      bub1 = size*119;
      for (i=bub0,j=bub0*2;i<=bub1;i++,j+=2*size) 
	XDrawArc(vu->dp,vu->wn,vu->gc,vu->center_x - i,vu->center_y - i,j,j,135*64 - redx,redy);
      XSetForeground(vu->dp,vu->gc,(ss->sgx)->black);
    }
}

static void Meter_Display_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  display_vu_meter((VU *)clientData);
}

static VU *make_vu_meter(Widget meter, int light_x, int light_y, int center_x, int center_y, Pixel needle_color, snd_state *ss, float size)
{
  VU *vu;
  vu_label *vl;
  XGCValues gv;
  vu = (VU *)calloc(1,sizeof(VU));
  vu->meter = meter;
  vu->size = size;
  XtVaGetValues(meter,XmNforeground, &gv.foreground,XmNbackground, &gv.background,NULL);
  vu->gc = XtGetGC(meter, GCForeground | GCBackground, &gv);
  vu->dp = XtDisplay(meter);
  vu->wn = XtWindow(meter);
  vu->scr = DefaultScreen(vu->dp);
  vu->on_off = VU_OFF;
  vu->current_val = 0.0;
  vu->last_val = 0.0;
  vu->clipped = 0;
  vu->max_val = 0.0;
  vu->light_x = light_x*size;
  vu->light_y = light_y*size;
  vu->center_x = center_x*size;
  vu->center_y = center_y*size;
  vu->ss = ss;
  vl = get_vu_label(ss,size);
  vu->on_label = vl->on_label;
  vu->off_label = vl->off_label;
  vu->clip_label = vl->clip_label;
  return(vu);
}

static void set_vu_val (VU *vu, float val) 
{
  vu->last_val = vu->current_val;
  vu->current_val = val;
  display_vu_meter(vu);
  if (val > vu->max_val)
    {
      vu->max_val = val;
      sprintf(timbuf,"%.3f",val);
      make_name_label(vu->max_button,timbuf);
    }
}


/* -------------------------------- AMP SLIDER CALLBACKS -------------------------------- */

#define INPUT_AMP 0
#define OUTPUT_AMP 1

static char record_one[5]={'1',snd_string_decimal,'0','0','\0'};
static char record_zero[5]={'0',snd_string_decimal,'0','0','\0'};
static char amp_number_buffer[5]={'1',snd_string_decimal,'0','0','\0'};

#define RECORD_SCROLLBAR_MAX 300
#define RECORD_SCROLLBAR_MID 150
#define RECORD_SCROLLBAR_LINEAR_MAX 50
#define RECORD_SCROLLBAR_LINEAR_MULT 0.00365368
#define RECORD_SCROLLBAR_CHANGEOVER_VALUE 0.188
/* final low end portion is linear -- multiplier is (exp-value-at-linear-max) / linear_max */

static void record_amp_changed(AMP *ap, int val)
{
  char *sfs;
  snd_state *ss;
  float amp;
  ss = ap->ss;
  if (val == 0) 
    amp = 0.0;
  else 
    {
      if (val < RECORD_SCROLLBAR_LINEAR_MAX)
	amp = (float)val * RECORD_SCROLLBAR_LINEAR_MULT;
      else amp = exp((float)(val-RECORD_SCROLLBAR_MID)/((float)RECORD_SCROLLBAR_MAX*.2));
    }
  sfs=prettyf(amp,2);
  fill_number(sfs,amp_number_buffer);
  make_name_label(ap->number,amp_number_buffer);
  free(sfs);
  if (ap->type == INPUT_AMP)
    rec_in_amps[ap->in][ap->out] = amp;
  else rec_out_amps[ap->out] = amp;
}

static int amp_to_slider(float val)
{
  /* reverse calc above */
  if (val <= 0.0) 
    return(0);
  else
    {
      if (val <= RECORD_SCROLLBAR_CHANGEOVER_VALUE)
	return(val / RECORD_SCROLLBAR_LINEAR_MULT);
      else return(RECORD_SCROLLBAR_MID + ((RECORD_SCROLLBAR_MAX * .2) * log(val)));
    }
}

static float global_amp(AMP *a)
{
  if (a->type == INPUT_AMP)
    return(rec_in_amps[a->in][a->out]);
  else return(rec_out_amps[a->out]);
}

static char *amp_to_string(float val)
{
  char *sfs;
  if (val == 0.0) return(record_zero);
  else if (val == 1.0) return(record_one);
  sfs=prettyf(val,2);
  fill_number(sfs,amp_number_buffer);
  free(sfs);
  return(amp_number_buffer);
}

static void Record_Amp_Click_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)callData;
  AMP *ap = (AMP *)clientData;
  XButtonEvent *ev;
  int val;
  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (snd_ControlMask | snd_MetaMask)) val = ap->last_amp; else val = RECORD_SCROLLBAR_MID;
  record_amp_changed(ap,val);
  XtVaSetValues(ap->slider,XmNvalue,val,NULL);
}

static void Record_Amp_Drag_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  record_amp_changed((AMP *)clientData,((XmScrollBarCallbackStruct *)callData)->value);
}

static void Record_Amp_ValueChanged_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)callData;
  AMP *ap = (AMP *)clientData;
  ap->last_amp = cb->value;
  record_amp_changed(ap,cb->value);
}



/* ---------------- MESSAGE PANE ---------------- */

static void Messages_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
	   "Message Pane",
"This pane contains any messages the recorder\n\
generates.  Each is time stamped.  Many are merely\n\
informational or mild gripes.\n\
");
}

static Widget make_message_pane(snd_state *ss, Widget message_pane)
{
  int n;
  Arg args[32];
  Widget msg;
  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNeditMode,XmMULTI_LINE_EDIT); n++;
  XtSetArg(args[n],XmNscrollBarDisplayPolicy,XmAS_NEEDED); n++;
  XtSetArg(args[n],XmNeditable,FALSE); n++;
  XtSetArg(args[n],XmNautoShowCursorPosition,FALSE); n++;
  XtSetArg(args[n],XmNcursorPositionVisible,FALSE); n++;
  XtSetArg(args[n],XmNscrollingPolicy,XmAUTOMATIC); n++;
  msg = XmCreateScrolledText(message_pane,"scrolled-text",args,n);
  XtManageChild(msg);
  map_over_children(XtParent(msg),set_main_color_of_widget,(void *)ss);
  XtVaSetValues(msg,XmNbackground,(ss->sgx)->text,NULL);
  XtAddCallback(msg,XmNhelpCallback,Messages_Help_Callback,ss);
  return(msg);
}



/* ---------------- FILE INFO PANE ---------------- */

static char *Device_Name(int dev)
{
  /* format label at top of pane */
  switch (dev)
    {
    case DIGITAL_OUT_DEVICE: return(snd_string_Digital_Out); break;
    case LINE_OUT_DEVICE:    return(snd_string_Line_Out); break;
    case DEFAULT_DEVICE: 
    case DAC_OUT_DEVICE:     return(snd_string_Output); break;
    case READ_WRITE_DEVICE: 
    case SPEAKERS_DEVICE:    return(snd_string_Speakers); break;
    case ADAT_IN_DEVICE:     return(snd_string_Adat_In); break;
    case AES_IN_DEVICE:      return(snd_string_Aes_In); break;
#if HAVE_OSS
    case LINE_IN_DEVICE:     return(snd_string_Analog_In); break;
#else
    case LINE_IN_DEVICE:     return(snd_string_Line_In); break;
#endif
    case MICROPHONE_DEVICE:  return(snd_string_Microphone); break;
    case DIGITAL_IN_DEVICE:  return(snd_string_Digital_In); break;
    case ADAT_OUT_DEVICE:    return(snd_string_Adat_Out); break;
    case AES_OUT_DEVICE:     return(snd_string_Aes_Out); break;
    case DAC_FILTER_DEVICE:  return("Tone"); break;
    case MIXER_DEVICE:       return("Mixer"); break;
    case AUX_INPUT_DEVICE:   return("Aux Input"); break;
    case CD_IN_DEVICE:       return("CD"); break;
    case AUX_OUTPUT_DEVICE:  return("Aux Output"); break;
    default:                 return(snd_string_Input); break;
    }
}

static char sysdevstr[32];
static char *System_and_Device_Name(int sys, int dev)
{
  if (strcmp(audio_system_name(sys),"OSS") == 0) return(Device_Name(dev));
  sprintf(sysdevstr,"%s: %s",audio_system_name(sys),Device_Name(dev));
  return(sysdevstr);
}

static int input_device(int dev)
{
  switch (dev)
    {
    case DIGITAL_OUT_DEVICE:
    case LINE_OUT_DEVICE:
    case DEFAULT_DEVICE:
    case ADAT_OUT_DEVICE:
    case AES_OUT_DEVICE:
    case SPEAKERS_DEVICE:
    case MIXER_DEVICE:
    case DAC_FILTER_DEVICE:
    case DAC_OUT_DEVICE: return(0); break;
    case READ_WRITE_DEVICE: 
    case ADAT_IN_DEVICE: 
    case AES_IN_DEVICE:
    case LINE_IN_DEVICE: 
    case MICROPHONE_DEVICE: 
    case DIGITAL_IN_DEVICE: 
    default: return(1); break;
    }
}

static int output_device(int dev)
{
  return((dev != DAC_FILTER_DEVICE) && (dev != MIXER_DEVICE) && (!(input_device(dev))));
}


static void file_label_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
	   "Output File Name",
"This field sets the name of the output file.\n\
");	   
}

static void duration_label_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
	   "Output File Duration",
"This field shows the duration of the current\n\
or previous recording.\n\
");	   
}

static void button_holder_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
	   "Record Options",
"These buttons configure the appearance of the recorder;\n\
'Autoload Recording', if set, causes the recorded output to be\n\
loaded automatically into Snd.\n\
");
}

static void autoload_file_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
       "Autoload File",
"If this button is set, the recorded file\n\
is automatically loaded into Snd.\n\
");	   
}

static void Help_Record_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
#if HAVE_XmHTML
  snd_help(ss,"Record","#recordfile");
#else
  ssnd_help(ss,
	    "Record",
	    get_recording_help(),
	    "\n\
If you go to the main Snd window while the\n\
recorder is active and play a sound, the\n\
recorder's audio lines are made inactive\n\
to try to reduce confusion.  To re-activate\n\
the recorder, press the 'reset' button at\n\
the bottom of the window.\n\
",
	    NULL);
#endif
}

static void VU_Reset_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
	   "Reset Button",
"This button resets the fields above it that\n\
indicate the on-going max amp encountered\n\
since the last reset\n\
");
}

static void device_button_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  PANE *p = (PANE *)clientData;
  int on,button;
#ifdef SGI
  int other_button,j,n,i;
  snd_state *ss;
#endif

#ifdef SGI
  ss = p->ss;
#endif

  XtVaGetValues(w,XmNuserData,&button,NULL);
  on = XmToggleButtonGetState(w);
  if (on)
    XtVaSetValues(p->pane,XmNpaneMinimum,p->pane_size,NULL);
  else 
    {
      XtVaGetValues(p->pane,XmNheight,&(p->pane_size),NULL);
      XtVaSetValues(p->pane,XmNpaneMaximum,1,NULL);
    }
  XtVaSetValues(p->pane,XmNpaneMinimum,1,XmNpaneMaximum,p->pane_size,NULL);

#if OLD_SGI_AL
  /* on the older SGI's (and maybe newer Indy's?) digital input disables mic/line-in and vice versa */
  if (button == digital_in_button)
    {
      set_line_source(p->ss,on);
      if (on == (XmToggleButtonGetState(device_buttons[microphone_button])))
	XmToggleButtonSetState(device_buttons[microphone_button],!on,TRUE); 
      if (on == (XmToggleButtonGetState(device_buttons[line_in_button])))
	XmToggleButtonSetState(device_buttons[line_in_button],!on,TRUE); 
    }
  else
    {
      if (button == microphone_button)
	{
	  if (on == (XmToggleButtonGetState(device_buttons[digital_in_button])))
	    {
	      XmToggleButtonSetState(device_buttons[digital_in_button],!on,TRUE); 
	      if (!(on == (XmToggleButtonGetState(device_buttons[line_in_button]))))
		XmToggleButtonSetState(device_buttons[line_in_button],on,TRUE); 
	    }
	}
    }
#endif
#if NEW_SGI_AL
  if (on)
    {
      if (active_device_button != -1)
	{
	  p = all_panes[active_device_button];
	  for (i=p->in_chan_loc,j=0;j<p->in_chans;j++,i++) in_device_on[i]=0;
	  if (active_device_button != button)
	    {
	      XtVaGetValues(p->pane,XmNheight,&(p->pane_size),NULL);
	      XtVaSetValues(p->pane,XmNpaneMaximum,1,NULL);
	      XtVaSetValues(p->pane,XmNpaneMinimum,1,XmNpaneMaximum,p->pane_size,NULL);
	      XmToggleButtonSetState(device_buttons[active_device_button],FALSE,FALSE); 
	    }
	  if (audio_open) snd_close_audio(0);
	  active_device_button = -1;
	}
      active_device_button = button;
      p = all_panes[button];
      for (i=p->in_chan_loc,j=0;j<p->in_chans;j++,i++) in_device_on[i]=1;
      input_channels[0] = p->in_chans;
      record_fd[0] = open_audio_input(AUDIO_SYSTEM(0) | p->device,
				      recorder_srate(ss),input_channels[0],recorder_in_format(ss),recorder_buffer_size(ss));
      if (record_fd[0] == -1)
	record_report(messages,Device_Name(p->device),": ",audio_error_name(audio_error()),NULL);
      else
	{
	  audio_open = 1;
	  if (!monitor_open)
	    {
	      monitor_fd = open_audio_output(AUDIO_SYSTEM(0) | DAC_OUT_DEVICE,
					     recorder_srate(ss),audio_out_chans,recorder_out_format(ss),recorder_buffer_size(ss));
	      if (monitor_fd == -1)
		{
		  record_report(messages,"open output: ",audio_error_name(audio_error()),NULL);
		  monitor_open = 0;
		}
	      else monitor_open = 1;
	    }
	  data_is_compatible = (recorder_in_format(ss) == COMPATIBLE_FORMAT);
	  set_read_in_progress(ss);
	}
    }
#endif
}

static void autoload_file_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  in_set_recorder_autoload(ss,XmToggleButtonGetState(w));
}

#if HAVE_OSS
static void save_audio_settings_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  save_audio_state();
  XmToggleButtonSetState(w,FALSE,FALSE);
  settings_saved = 1;
  write_mixer_state(MIXER_SAVE_FILE);
}

static void save_audio_settings_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
       "Save Audio Settings",
"Normally, Snd saves the state of the audio hardware\n\
(the 'mixer' in Linux jargon) before opening the\n\
recorder window, then restores it upon closing that\n\
window.  This means that any changes you make via\n\
the sliders will be erased upon exit.  To save the\n\
current state, press this button.  The mixer state\n\
will be written to the file .snd-mixer which can be\n\
deleted to cancel the save.  This file will also be\n\
read by CLM, someday.\n\
");	   
}
#endif

static void Srate_Changed_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  char *str;
  int n;
  snd_state *ss = (snd_state *)clientData;
  str = XmTextGetString(w); 
  if (str) 
    {
      sscanf(str,"%d",&n);
      if ((n>0) && (n != recorder_srate(ss)))
	{
	  in_set_recorder_srate(ss,n);
	  set_audio_srate(ss,DEFAULT_DEVICE,recorder_srate(ss),0);
	}	
    }
}

static void set_record_size (snd_state *ss, int new_size);

static void Rec_Size_Changed_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  char *str;
  int n;
  str = XmTextGetString(w); 
  if (str) 
    {
      sscanf(str,"%d",&n);
      if ((n>0) && (n != recorder_buffer_size(ss))) set_record_size(ss,n);
    }
}

static void rec_size_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  snd_help(ss,
	   "Record Input Buffer Size",
"This field sets the size of the recorder's input\n\
buffers.  If you are getting clicks, and have already\n\
tried everything else (you're writing to a local disk,\n\
in the host's native data format), try goofing around\n\
with this number.\n\
");
}

static void Record_Header_Type_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  int pos,hpos,dpos,type = 0,format = 0;
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)callData;
  pos = cbs->item_position;
  switch (pos)
    {
    case 1: type = NeXT_sound_file; format = snd_16_linear; break;
    case 2: type = AIFF_sound_file; format = snd_16_linear; break;
    case 3: type = RIFF_sound_file; format = snd_16_linear_little_endian; break;
    case 4: type = IRCAM_sound_file; format = snd_16_linear; break;
    case 5: type = raw_sound_file; format = COMPATIBLE_FORMAT; break;
    }
  load_header_and_data_lists(header_list,format_list,type,format,&hpos,&dpos);
}

static int make_file_info_pane(snd_state *ss, Widget file_pane, int *ordered_devices, int ndevs, int *ordered_systems)
{
  int i,n,init_n;
  Position pane_max;
  Arg args[32];
  char *name;
  Widget *fwids;
  Widget file_label,file_form,button_frame,button_holder,duration_label,rec_size_label,
    ff_form,ff_sep1,ff_sep2,ff_sep3,ff_sep4,autoload_file,fd_form;
#if HAVE_OSS
  Widget save_audio_settings;
#endif
#ifdef SGI
  float val[1];
  int err;
#endif

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  file_form = XtCreateManagedWidget("file-data",xmFormWidgetClass,file_pane,args,n);

  n=0;
  if (!(ss->using_schemes)) {XtSetArg(args[n],XmNbackground,(ss->sgx)->black); n++;}
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++;
  XtSetArg(args[n],XmNseparatorType,XmDOUBLE_LINE); n++;
  XtSetArg(args[n],XmNheight,15); n++;
  ff_sep4 = XtCreateManagedWidget("ff-sep4",xmSeparatorWidgetClass,file_form,args,n);
      
  /* file data */
  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep4); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
  ff_form = XtCreateManagedWidget("ff-form",xmFormWidgetClass,file_form,args,n);

  n=0;
  if (!(ss->using_schemes)) {XtSetArg(args[n],XmNbackground,(ss->sgx)->red); n++;}
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
  /* XtSetArg(args[n],XmNheight,34); n++; */
  XtSetArg(args[n],XmNmarginTop,8); n++;
  XtSetArg(args[n],XmNmarginBottom,8); n++;
  file_label = XtCreateManagedWidget(snd_string_file_p,xmLabelWidgetClass,ff_form,args,n);
  XtAddCallback(file_label,XmNhelpCallback,file_label_help_callback,ss);

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,file_label); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,file_label); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  if (snd_strlen(recorder_file(ss)) > 0) {XtSetArg(args[n],XmNvalue,recorder_file(ss)); n++;}
  file_text = sndCreateTextFieldWidget(ss,"text",ff_form,args,n,NOT_ACTIVATABLE);
  XtAddCallback(file_text,XmNhelpCallback,file_label_help_callback,ss);

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,file_text); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++;
  XtSetArg(args[n],XmNheight,10); n++;
  ff_sep3 = XtCreateManagedWidget("ff-sep3",xmSeparatorWidgetClass,ff_form,args,n);      

  /* this ought to be redundant, but both the SGI and Linux need it to keep from clobbering the file_text widget */
  n=0;
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep3); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  fd_form = XtCreateManagedWidget("fd-form",xmFormWidgetClass,ff_form,args,n);

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  fwids = sndCreateFileDataForm(ss,fd_form,"data-form",args,n,TRUE,TRUE,out_type,recorder_out_format(ss),FALSE);
  header_list = fwids[fdata_hlist];
  format_list = fwids[fdata_dlist];
  srate_text = fwids[fdata_stext];
  chans_text = fwids[fdata_ctext];
  comment_text = fwids[fdata_comment_text];
  XtVaGetValues(comment_text,XmNy,&pane_max,NULL);
  XtAddCallback(header_list,XmNbrowseSelectionCallback,Record_Header_Type_Callback,fwids);
  XtAddCallback(srate_text,XmNactivateCallback,Srate_Changed_Callback,(void *)ss);
#ifdef SGI
  err = read_audio_state(AUDIO_SYSTEM(0) | MICROPHONE_DEVICE,SRATE_FIELD,0,val);
  if (!err) in_set_recorder_srate(ss,val[0]);
#endif
  sprintf(timbuf,"%d",recorder_srate(ss));
  XmTextSetString(srate_text,timbuf);
  sprintf(timbuf,"%d",recorder_out_chans(ss));
  XmTextSetString(chans_text,timbuf);
  if (!(ss->using_schemes))
    {
      map_over_children(file_form,set_main_color_of_widget,ss);
      /* for (i=0;i<FILE_DATA_WIDGETS;i++) if (fwids[i]) XmChangeColor(fwids[i],(ss->sgx)->main); */
      /* map_over_children(XtParent(format_list),set_main_color_of_widget,(void *)ss); */
      XtVaSetValues(header_list,XmNbackground,(ss->sgx)->white,NULL);
      XtVaSetValues(format_list,XmNbackground,(ss->sgx)->white,NULL);
    }

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep4); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,ff_form); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNorientation,XmVERTICAL); n++;
  XtSetArg(args[n],XmNwidth,15); n++;
  ff_sep1 = XtCreateManagedWidget("ff-sep1",xmSeparatorWidgetClass,file_form,args,n);      

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep4); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,ff_sep1); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNmarginTop,8); n++;
  XtSetArg(args[n],XmNmarginBottom,8); n++;
  duration_label = XtCreateManagedWidget(snd_string_duration_p,xmLabelWidgetClass,file_form,args,n);
  XtAddCallback(duration_label,XmNhelpCallback,duration_label_help_callback,ss);

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep4); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrecomputeSize,FALSE); n++;
  XtSetArg(args[n],XmNcolumns,6); n++;
  rec_size_text = sndCreateTextFieldWidget(ss,"rectext",file_form,args,n,NOT_ACTIVATABLE);
  XtAddCallback(rec_size_text,XmNhelpCallback,rec_size_help_callback,ss);
  XtAddCallback(rec_size_text,XmNactivateCallback,Rec_Size_Changed_Callback,(void *)ss);
  sprintf(timbuf,"%d",recorder_buffer_size(ss));
  XmTextSetString(rec_size_text,timbuf);

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep4); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNrightWidget,rec_size_text); n++;
  XtSetArg(args[n],XmNmarginTop,8); n++;
  XtSetArg(args[n],XmNmarginBottom,8); n++;
  rec_size_label = XtCreateManagedWidget("buf:",xmLabelWidgetClass,file_form,args,n);
  XtAddCallback(rec_size_label,XmNhelpCallback,rec_size_help_callback,ss);

  n=0;
  if (!(ss->using_schemes)) n = background_text_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep4); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,duration_label); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNrightWidget,rec_size_label); n++;
  XtSetArg(args[n],XmNrecomputeSize,FALSE); n++;
  XtSetArg(args[n],XmNmarginTop,8); n++;
  XtSetArg(args[n],XmNmarginBottom,8); n++;
  file_duration = XtCreateManagedWidget("  0.0 ",xmLabelWidgetClass,file_form,args,n);
  XtAddCallback(file_duration,XmNhelpCallback,duration_label_help_callback,ss);

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,rec_size_text); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,ff_sep1); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++;
  XtSetArg(args[n],XmNheight,10); n++;
  ff_sep2 = XtCreateManagedWidget("ff-sep2",xmSeparatorWidgetClass,file_form,args,n);      

  /* buttons */
  n=0;
  if (!(ss->using_schemes)) n = background_zoom_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,ff_sep2); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,ff_sep1); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNshadowThickness,4); n++;
  button_frame = XtCreateManagedWidget("button-frame",xmFrameWidgetClass,file_form,args,n);

  n=0;
  if (!(ss->using_schemes)) n = background_high_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNorientation,XmVERTICAL); n++;
  XtSetArg(args[n],XmNspacing,0); n++;
  button_holder = XtCreateManagedWidget("button-holder",xmRowColumnWidgetClass,button_frame,args,n);
  XtAddCallback(button_holder,XmNhelpCallback,button_holder_help_callback,ss);

  /* load up the box of panel on-buttons and various other settings (autoload for now) */
  n=0;
  if (!(ss->using_schemes)) 
    {
      n = background_high_color(args,n,ss);
      XtSetArg(args[n],XmNselectColor,(ss->sgx)->red); n++;
    }
  init_n = n;
  for (i=0;i<ndevs;i++)
    {
      n = init_n;
      XtSetArg(args[n],XmNuserData,i); n++;
#if OLD_SGI_AL
      if ((ordered_devices[i] == DIGITAL_IN_DEVICE) || (ordered_devices[i] == MICROPHONE_DEVICE))
	{XtSetArg(args[n],XmNindicatorType,XmONE_OF_MANY); n++;}
      else {XtSetArg(args[n],XmNindicatorType,XmN_OF_MANY); n++;}
#endif
#if NEW_SGI_AL
      if (input_device(ordered_devices[i]))
	{XtSetArg(args[n],XmNindicatorType,XmONE_OF_MANY); n++;}
      else {XtSetArg(args[n],XmNindicatorType,XmN_OF_MANY); n++;}
#endif
      if ((systems == 1) || (!(input_device(ordered_devices[i]))))
	name = Device_Name(ordered_devices[i]);
      else name = System_and_Device_Name(ordered_systems[i],ordered_devices[i]);
      device_buttons[i] = XtCreateManagedWidget(name,xmToggleButtonWidgetClass,button_holder,args,n);
#if OVERRIDE_TOGGLE
      override_toggle_translation(device_buttons[i]);
#endif
      XtAddCallback(device_buttons[i],XmNhelpCallback,button_holder_help_callback,ss);
      XtAddCallback(device_buttons[i],XmNvalueChangedCallback,device_button_callback,(XtPointer)all_panes[i]);
      XmToggleButtonSetState(device_buttons[i],TRUE,FALSE); 
    }
  autoload_file = XtCreateManagedWidget(snd_string_Autoload_Recording,xmToggleButtonWidgetClass,button_holder,args,init_n);
#if OVERRIDE_TOGGLE
  override_toggle_translation(autoload_file);
#endif
  device_buttons[ndevs] = autoload_file;
  autoload_button = ndevs;
  XtAddCallback(autoload_file,XmNhelpCallback,autoload_file_help_callback,ss);
  XtAddCallback(autoload_file,XmNvalueChangedCallback,autoload_file_callback,NULL);
  XmToggleButtonSetState(autoload_file,recorder_autoload(ss),FALSE); 
#if HAVE_OSS
  save_audio_settings = XtCreateManagedWidget(snd_string_Save_Audio_Settings,xmToggleButtonWidgetClass,button_holder,args,n);
#if OVERRIDE_TOGGLE
  override_toggle_translation(save_audio_settings);
#endif
  XtAddCallback(save_audio_settings,XmNvalueChangedCallback,save_audio_settings_callback,NULL);
  XtAddCallback(save_audio_settings,XmNhelpCallback,save_audio_settings_help_callback,ss);
#endif
  return(pane_max + 50);
}

static void update_duration(float new_dur)
{
  sprintf(timbuf,"%.2f",new_dur);
  make_name_label(file_duration,timbuf);
}


int record_in_progress(void)
{
  return((recorder) && (recording));
}

void lock_recording_audio(void)
{
  if ((recorder) && (XtIsManaged(recorder)))
    {
      snd_close_audio(0);
      XtSetSensitive(record_button,FALSE);
      XtSetSensitive(reset_button,FALSE);
    }
}

void unlock_recording_audio(void)
{
  XmString s1;
  if ((recorder) && (XtIsManaged(recorder)))
    {
      XtSetSensitive(record_button,TRUE);
      XtSetSensitive(reset_button,TRUE);
      XmProcessTraversal(reset_button,XmTRAVERSE_CURRENT);
      s1 = XmStringCreate(snd_string_Restart,XmFONTLIST_DEFAULT_TAG);
      XtVaSetValues(reset_button,XmNlabelString,s1,NULL);
      XmStringFree(s1);
    }
}


/* -------------------------------- DEVICE PANE -------------------------------- */


static char *device_name(PANE *p)
{
  /* informal aw shucks reference in help window */
  switch (p->device)
    {
    case DIGITAL_OUT_DEVICE:
    case LINE_OUT_DEVICE:
    case DEFAULT_DEVICE:
    case DAC_OUT_DEVICE:
    case READ_WRITE_DEVICE: return("the output");                    break;
    case SPEAKERS_DEVICE:   return("the speakers");                  break;
    case ADAT_OUT_DEVICE: 
    case ADAT_IN_DEVICE:    return("the Adat");                      break;
    case AES_OUT_DEVICE: 
    case AES_IN_DEVICE:     return("the Aes");                       break;
    case LINE_IN_DEVICE:    return("line in");                       break;
    case MICROPHONE_DEVICE: return("the microphone");                break;
    case DIGITAL_IN_DEVICE: return("digital in");                    break;
    case DAC_FILTER_DEVICE: return("the analog tone control");       break;
    case MIXER_DEVICE:      return("various analog volume controls");break;
    default:                return("the input");                     break;
    }
}

#if (!HAVE_OSS)
static char *channel_function(PANE *p)
{
  if (input_device(p->device)) return("gain"); else return("volume");
}

static char funbuf[16];
static char *device_function(PANE *p)
{
  sprintf(funbuf,"%s %s",Device_Name(p->device),channel_function(p));
  return(funbuf);
}
#endif

static char numbuf[8];
static char *channel_name(PANE *p, int chan)
{
  int use_numbers;
  use_numbers = ((p->out_chans>4) || (p->in_chans>4));
  if (use_numbers)
    sprintf(numbuf,"%d",chan+1);
  else sprintf(numbuf,"%c",(char)('A' + chan));
  return(numbuf);
}

static char *out_channel_name(snd_state *ss, int chan)
{
  int use_numbers;
  use_numbers = (recorder_out_chans(ss)>4);
  if (use_numbers)
    sprintf(numbuf,"%d",chan+1);
  else sprintf(numbuf,"%c",(char)('A' + chan));
  return(numbuf);
}

static char *gain_channel_name(PANE *p, int input, int dev_in, int out)
{
  int use_numbers;
  if (input)
    {
      use_numbers = ((p->out_chans>4) || (p->in_chans>4));
      if (use_numbers)
	sprintf(numbuf,"%d->%d:",dev_in+1,out+1);
      else sprintf(numbuf,"%c->%c:",(char)('A' + dev_in),(char)('A'+out));
    }
  else
    {
      use_numbers = (p->out_chans > 4);
      if (use_numbers)
	sprintf(numbuf,"%d:",out+1);
      else sprintf(numbuf,"%c:",(char)('A'+out));
    }
  return(numbuf);
}

static void VU_Max_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  Wdesc *wd = (Wdesc *)clientData;
  ssnd_help(wd->ss,
	   "Max Amp Indicator",
	    "This number indicates the max amp encountered\n\
since the last reset in ",
	    device_name(wd->p),"'s",
	    " channel ",
	    channel_name(wd->p,wd->chan),
	    ".\n\
The reset button sets it back to 0.0.\n",
	    NULL);
}

static void VU_On_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  Wdesc *wd = (Wdesc *)clientData;
  ssnd_help(wd->ss,
	    "On/Off Button",
	    "This button causes ",
	    device_name(wd->p),"'s",
	    " channel ",
	    channel_name(wd->p,wd->chan),
	    " to be included\n\
in or removed from the recording. The button\n\
is red when the signal is active.\n",
	    NULL);
}

#if HAVE_OSS
static char *field_abbreviation(int fld)
{
  switch (fld)
    {
    case IMIX_FIELD:   return("imx"); break;
    case IGAIN_FIELD:  return("ign"); break;
    case RECLEV_FIELD: return("rec"); break;
    case PCM_FIELD:    return("pcm"); break;
    case PCM2_FIELD:   return("pc2"); break;
    case OGAIN_FIELD:  return("ogn"); break;
    case LINE_FIELD:   return("lin"); break;
    case MIC_FIELD:    return("mic"); break;
    case LINE1_FIELD:  return("l1");  break;
    case LINE2_FIELD:  return("l2");  break;
    case LINE3_FIELD:  return("l3");  break;
    case SYNTH_FIELD:  return("syn"); break;
    case BASS_FIELD:   return("ton"); break;
    case TREBLE_FIELD: return("ton"); break;
    }
  return("oops");
}

static char *field_name(int fld)
{
  switch (fld)
    {
    case IMIX_FIELD:   return("imix"); break;
    case IGAIN_FIELD:  return("igain"); break;
    case RECLEV_FIELD: return("reclev"); break;
    case PCM_FIELD:    return("pcm"); break;
    case PCM2_FIELD:   return("pcm2"); break;
    case OGAIN_FIELD:  return("ogain"); break;
    case LINE_FIELD:   return("line-in"); break;
    case MIC_FIELD:    return("mic"); break;
    case LINE1_FIELD:  return("line1"); break;
    case LINE2_FIELD:  return("line2"); break; 
    case LINE3_FIELD:  return("line3"); break;
    case SYNTH_FIELD:  return("synth"); break;
    default: return("?"); break;
    }
}

static char *field_function(int fld)
{
  switch (fld)
    {
    case IMIX_FIELD:   return("the pre-adc mix of mic and line-in"); break;
    case IGAIN_FIELD:  return("input gain"); break;
    case RECLEV_FIELD: return("recording level"); break;
    case PCM_FIELD:    return("the speaker level, perhaps"); break;
    case PCM2_FIELD:   return("nothing in particular"); break;
    case OGAIN_FIELD:  return("output gain"); break;
    case LINE_FIELD:   return("analog line-in"); break;
    case MIC_FIELD:    return("the microphone"); break;
    case LINE1_FIELD:  
    case LINE2_FIELD:  
    case LINE3_FIELD:  return("extra line inputs"); break;
    case SYNTH_FIELD:  return("the on-card synthesizer, if any"); break;
    default: return("?"); break;
    }
}
#endif

static void volume_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  Wdesc *wd = (Wdesc *)clientData;
#if HAVE_OSS
  int device,channel,field;
  device = wd->device;
  field = wd->field;
  channel = wd->chan;
  if (device == DAC_OUT_DEVICE)
    ssnd_help(wd->ss,
	      "Analog Out",
	      "This slider scales channel ",
	      out_channel_name(wd->ss,channel),
	      "'s volume\n\
at the speaker.  It has no effect on the output\n\
file's amplitude.\n",
	      NULL);
  else
    {
      if (device == DAC_FILTER_DEVICE)
	ssnd_help(wd->ss,
		  "Tone Controls",
		  "This slider sets the mixer's ",
		  (field == TREBLE_FIELD) ? "treble" : "bass",
		  " tone control.\n",
		  NULL);
      else
	{
	  if (device == MICROPHONE_DEVICE)
	    ssnd_help(wd->ss,
		      "Microphone Gain",
"This slider controls the input gain of the microphone.\n",
		      NULL);
	  else
	    {
	      if (device == LINE_IN_DEVICE)
		ssnd_help(wd->ss,
			  "Line In Gain",
"This slider controls the volume of the analog\n\
line-in channel ",
			  channel_name(wd->p,channel),
			  ".\n",
			  NULL);
	      else
		ssnd_help(wd->ss,
			  "Linux Mixer Settings",
"This bank of sliders affects the OSS 'mixer'; this\n\
particular slider claims to control channel ",
			  channel_name(wd->p,channel),
			  "'s\n",
			  field_name(field),
" field, which I believe has something to do with\n",
			  field_function(field),
			  ".\n",
			  NULL);
	    }
	}
    }
#else
  ssnd_help(wd->ss,
	    device_function(wd->p),
	    "This slider sets the ",
	    channel_function(wd->p),
	    " of channel ",
	    channel_name(wd->p,wd->chan),
	    " of ",
	    device_name(wd->p),
	    "\n",
	    (input_device(((PANE *)(wd->p))->device)) ? "This scales the in-coming signal before it\n\
is reflected in the meters or the audio data.\n" : "",
	    NULL);
#endif
}

static void amp_slider_help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  Wdesc *wd = (Wdesc *)clientData;
  PANE *p;
  AMP *a;
  p = wd->p;
  a = p->amps[wd->chan];
  ssnd_help(wd->ss,
	    "Volume Slider",
	    "This slider scales ",
	    (input_device(((PANE *)(wd->p))->device)) ? "the contribution of " : "",
	    device_name(wd->p),"'s\n",
	    "channel ",
	    channel_name(wd->p,a->in),
	    (input_device(((PANE *)(wd->p))->device)) ? " to the output file's channel " : ".",
	    (input_device(((PANE *)(wd->p))->device)) ? (out_channel_name(wd->ss,a->out)) : "",
	    NULL);
}

static void Meter_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  Wdesc *wd = (Wdesc *)clientData;
  ssnd_help(wd->ss,
	    "VU Meter",
	    "This meter shows the current volume of ",
	    device_name(wd->p),"'s",
	    " channel ",
	    channel_name(wd->p,wd->chan),
	    ".\n\
It is yellow if active, red if clipping.\n",
	    NULL);
}

static void VU_Reset_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* set current maxes to 0.0 */
  int i;
  PANE *p = (PANE *)clientData;
  VU *vu;
  for (i=0;i<p->meters_size;i++)
    {
      vu = p->meters[i];
      vu->max_val = 0.0;
      make_name_label(vu->max_button,"0.00");
    }
}

static void Meter_Button_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  Wdesc *wd = (Wdesc *)clientData;
  VU *vu;
  snd_state *ss;
  int val,i,n;
  char *str;
  PANE *p;
  p = wd->p;
  vu = p->meters[wd->chan];
  ss = vu->ss;
  if (vu->on_off == VU_OFF)
    {
      XmChangeColor(w,(Pixel)(ss->sgx)->red);
      vu->on_off = VU_ON;
      vu->red_deg = 0.0;
    }
  else 
    {
      XmChangeColor(w,(Pixel)(ss->sgx)->main);
      vu->on_off = VU_OFF;
    }
  display_vu_meter(vu);
  val = (vu->on_off == VU_ON);
  p->active[wd->chan] = val;
  if (output_device(p->device))
    {
      rec_out_active[wd->chan] = val;
      str = XmTextGetString(chans_text); 
      if (str) 
	sscanf(str,"%d",&n);
      else n=0;
      val = 0;
      for (i=0;i<p->active_size;i++) {if (p->active[i]) val++;}
      if ((val>0) && (val != n))
	{
	  sprintf(timbuf,"%d",val);
	  XmTextSetString(chans_text,timbuf);
	}
    }
  else rec_in_active[wd->gain] = val;
}

static void volume_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  Wdesc *wd = (Wdesc *)clientData;
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)callData;
  set_audio_gain(wd,(float)cbs->value/100.0);
}

int device_channels(int dev); /* audio.c */
int device_gains(int dev);

static PANE *make_pane(snd_state *ss, Widget paned_window, int device, int system)
{
  /* VU meters (frame, then drawing area widget) */
  /* Linux OSS complication -- the top input panel also has all the "mixer" input volume controls and the output pane has the tone controls, if any */
  Wdesc *wd;
  int n,i,k,chan,amp_sliders,temp_out_chan,temp_in_chan;
  Arg args[32];
  AMP *a;
  PANE *p;
  Widget frames[8];
  Widget frame,meter,last_frame,vu_vertical_sep,icon_label,last_slider,button_vertical_sep,max_label,
    button_label,button_box,button1_label,last_button,slider_sep,first_frame,last_max;
  VU *vu;
  int vu_meters,num_audio_gains,button_size,input,special_cases = 0;
  XmString labelstr,s1;
  Position pane_max;
  float meter_size,vol;
#if HAVE_OSS
  float mixer_field_chans[32];
  int mixflds[32];
  int last_device,this_device;
  XmString slabel = NULL;
#endif

  p = (PANE *)calloc(1,sizeof(PANE));
  p->device = device;
  p->ss = ss;
  vu_meters = device_channels(AUDIO_SYSTEM(system) | device);
  input = (input_device(device));
  num_audio_gains = device_gains(AUDIO_SYSTEM(system) | device);
#if HAVE_OSS
  if ((input) && (!mixer_gains_posted[system]))
    {
      for (k=0;k<32;k++) mixer_field_chans[k] = 0.0;
      read_audio_state(AUDIO_SYSTEM(system) | MIXER_DEVICE,FORMAT_FIELD,32,mixer_field_chans);
      for (k=0;k<32;k++) mixflds[k] = (int)mixer_field_chans[k]; /* simplify life later */
      mixer_gains_posted[system] = device_gains(AUDIO_SYSTEM(system) | MIXER_DEVICE);
      num_audio_gains = mixer_gains_posted[system]; /* includes the LINE_IN_DEVICE gains */
      special_cases = mixer_gains_posted[system];
    }
  if ((!input) && (!tone_controls_posted[system]))
    {
      tone_controls_posted[system] = device_gains(AUDIO_SYSTEM(system) | DAC_FILTER_DEVICE);
      num_audio_gains += tone_controls_posted[system];
      special_cases = tone_controls_posted[system];
    }
  last_device = MIC_FIELD;
#endif

  if (input) 
    {
      p->in_chans = vu_meters;
      p->out_chans = recorder_out_chans(ss);
    }
  else 
    {
      if (vu_meters < recorder_out_chans(ss)) vu_meters = recorder_out_chans(ss);
      p->out_chans = vu_meters;
    }
  p->meters = (VU **)calloc(vu_meters,sizeof(VU *));
  p->meters_size = vu_meters;
  p->active = (int *)calloc(vu_meters,sizeof(int));
  p->active_size = vu_meters;

  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNallowResize,TRUE); n++;
  p->pane = XtCreateManagedWidget("pane",xmFormWidgetClass,paned_window,args,n);
  
  meter_size = vu_size(ss);
  if (vu_meters > 2) meter_size *= .75; else if (vu_meters > 4) meter_size *= .5;

  last_frame = NULL;
  first_frame = NULL;
  for (i=0;i<vu_meters;i++)
    {
      n=0;
      if (!(ss->using_schemes)) {XtSetArg(args[n],XmNbackground,(ss->sgx)->black); n++;}
      if (i<4)
	{
	  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
	}
      else
	{
	  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNtopWidget,frames[i-4]); n++;
	}
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
      if (!last_frame) 
	{
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
	}
      else 
	{
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNleftWidget,last_frame); n++;
	}
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNshadowType,XmSHADOW_ETCHED_IN); n++;
      if (vu_meters < 4)
	{XtSetArg(args[n],XmNshadowThickness,6); n++;}
      else {XtSetArg(args[n],XmNshadowThickness,3); n++;}
      frame = XtCreateManagedWidget("frame",xmFrameWidgetClass,p->pane,args,n);
      frames[i] = frame;
      if (!first_frame) first_frame = frame;

      n=0;
      XtSetArg(args[n],XmNbackground,(ss->sgx)->white); n++;
      XtSetArg(args[n],XmNforeground,(ss->sgx)->black); n++;
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNwidth,CENTER_X*2*meter_size); n++;
      XtSetArg(args[n],XmNheight,LIGHT_Y*meter_size); n++;
      XtSetArg(args[n],XmNallowResize,FALSE); n++;
      meter = XtCreateManagedWidget("vu",xmDrawingAreaWidgetClass,frame,args,n);
      p->meters[i] = make_vu_meter(meter,LIGHT_X,LIGHT_Y,CENTER_X,CENTER_Y,(ss->sgx)->black,ss,meter_size);
      vu = p->meters[i];
      if (input)
	rec_in_VU[overall_input_ctr+i] = vu;
      else rec_out_VU[i] = vu;
      XtAddCallback(meter,XmNexposeCallback,Meter_Display_Callback,vu);
      XtAddCallback(meter,XmNresizeCallback,Meter_Display_Callback,vu);
      wd = (Wdesc *)calloc(1,sizeof(Wdesc));
      wd->chan = i;
      wd->ss = ss;
      wd->p = p;
      wd->field = AMP_FIELD;
      wd->device = device;
      wd->system = system;
      XtAddCallback(meter,XmNhelpCallback,Meter_Help_Callback,wd);
      last_frame = frame;
      if ((i==3) && (vu_meters>4)) 
	{
	  last_frame = NULL;
	  first_frame = NULL;
	}
    }
  
  /* if no audio (hardware) gains, we have the vu separator and the control buttons */

  /* vertical scalers on the right (with icon) */
  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
  XtSetArg(args[n],XmNbottomWidget,last_frame); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,last_frame); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNseparatorType,XmNO_LINE); n++;
  XtSetArg(args[n],XmNorientation,XmVERTICAL); n++;
  if (vu_meters < 4)
    {XtSetArg(args[n],XmNwidth,10); n++;}
  else {XtSetArg(args[n],XmNwidth,4); n++;}
  vu_vertical_sep = XtCreateManagedWidget("sep",xmSeparatorWidgetClass,p->pane,args,n);

  if (num_audio_gains > 0)
    {
      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNlabelType,XmPIXMAP); n++;
#if HAVE_OSS
      if (input)
	{XtSetArg(args[n],XmNlabelPixmap,device_icon((mixflds[MIC_FIELD]>0) ? MICROPHONE_DEVICE : LINE_IN_DEVICE)); n++;}
      else {XtSetArg(args[n],XmNlabelPixmap,device_icon(device)); n++;}
      if (input)
	{
	  if (mixflds[MIC_FIELD]<=0)
	    last_device = LINE_FIELD;
	}
      else last_device = DAC_OUT_DEVICE;
#else
      XtSetArg(args[n],XmNlabelPixmap,device_icon(device)); n++;
#endif
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      if (num_audio_gains > 1)
	{XtSetArg(args[n],XmNalignment,XmALIGNMENT_CENTER); n++;}
      else {XtSetArg(args[n],XmNalignment,XmALIGNMENT_END); n++;}
#if HAVE_OSS
      if (input)
	{XtSetArg(args[n],XmNwidth,(mixflds[last_device] == 2) ? 30 : 15); n++;}
      else {XtSetArg(args[n],XmNwidth,30); n++;}
#else
      XtSetArg(args[n],XmNwidth,30); n++;
#endif
      icon_label = XtCreateManagedWidget("icon",xmLabelWidgetClass,p->pane,args,n);
      
      last_slider = NULL;
      for (i=0,chan=num_audio_gains-1;i<num_audio_gains;i++,chan--)
	{
	  /* we're moving right to left here, so the first slider represents the highest channel */
	  /* in the Linux case, as each new device pops up, we need some indication above it */
	  wd = (Wdesc *)calloc(1,sizeof(Wdesc));
	  wd->system = system;
#if HAVE_OSS
	  /* we're moving right to left here, chan is counting down, we need to fill out MIXER|DAC_FILTER_DEVICE fields and channels */
	  /* and also handle whatever else comes along */
	  /* treble and bass are actually stereo -- we'll do both at once */
	  if (!input)
	    {
	      /* count back from speaker 1 0 treble bass */
	      switch (i)
		{
		case 0: 
		  wd->chan = 1;
		  wd->field = AMP_FIELD;
		  wd->device = DAC_OUT_DEVICE;
		  break;
		case 1: 
		  wd->chan = 0;
		  wd->field = AMP_FIELD;
		  wd->device = DAC_OUT_DEVICE;
		  break;
		case 2: 
		  wd->chan = 0;
		  wd->field = TREBLE_FIELD;
		  wd->device = DAC_FILTER_DEVICE;
		  break;
		case 3: 
		  wd->chan =0;
		  wd->field = BASS_FIELD;
		  wd->device = DAC_FILTER_DEVICE;
		  break;
		}
	      if (i>1) this_device = DAC_FILTER_DEVICE; else this_device = DAC_OUT_DEVICE;
	    }
	  else
	    {
	      /* we want speaker/line-in gains on the far right, then whatever else */
	      if (mixflds[MIC_FIELD] > 0)
		{
		  wd->chan = mixflds[MIC_FIELD]-1;
		  wd->field = AMP_FIELD;
		  wd->device = MICROPHONE_DEVICE;
		  mixflds[MIC_FIELD]--;
		  this_device = MIC_FIELD;
		}
	      else
		{
		  if (mixflds[LINE_FIELD] > 0)
		    {
		      wd->chan = mixflds[LINE_FIELD]-1;
		      wd->field = AMP_FIELD;
		      wd->device = LINE_IN_DEVICE;
		      mixflds[LINE_FIELD]--;
		      this_device = LINE_FIELD;
		    }
		  else
		    {
		      wd->chan = 0;
		      for (k=0;k<32;k++) /* MIXER_SIZE used for 32 in audio.c */
			{
			  if (mixflds[k] > 0)
			    {
			      wd->chan = mixflds[k]-1;
			      wd->field = k;
			      wd->device = MIXER_DEVICE;
			      mixflds[k]--;
			      this_device = k;
			      break;
			    }
			}
		    }
		}
	    }
#else
	  wd->chan = chan;
	  wd->field = AMP_FIELD;
	  wd->device = p->device;
#endif
	  wd->ss = ss;
	  wd->p = p;
	  wd->gain = gain_ctr+chan;
	  audio_GAINS[wd->gain] = wd;
	  vol = get_audio_gain(wd);
#if HAVE_OSS
	  if (last_device != this_device)
	    {
	      n=0;
	      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
	      if (this_device == LINE_FIELD)
		{
		  XtSetArg(args[n],XmNlabelType,XmPIXMAP); n++;
		  XtSetArg(args[n],XmNlabelPixmap,device_icon(LINE_IN_DEVICE)); n++;
		}
	      else
		{
		  if ((!input) && (this_device == DAC_FILTER_DEVICE))
		    slabel = XmStringCreate("ton","small_font");
		  else slabel = XmStringCreate(field_abbreviation(this_device),"small_font");
		  XtSetArg(args[n],XmNlabelString,slabel); n++;
		}
	      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
	      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
	      XtSetArg(args[n],XmNleftAttachment,XmATTACH_NONE); n++;
	      XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
	      XtSetArg(args[n],XmNrightWidget,icon_label); n++;
	      XtSetArg(args[n],XmNfontList,small_fontlist); n++;
	      XtSetArg(args[n],XmNalignment,XmALIGNMENT_CENTER); n++;
	      XtSetArg(args[n],XmNwidth,30); n++;
	      if (input)
		{XtSetArg(args[n],XmNwidth,(mixflds[this_device] == 1) ? 30 : 15); n++;} /* 1 because we subtracted one already above */
	      else {XtSetArg(args[n],XmNwidth,30); n++;}
	      icon_label = XtCreateManagedWidget("icon",xmLabelWidgetClass,p->pane,args,n);
	      if ((this_device != LINE_FIELD) && (slabel)) {XmStringFree(slabel); slabel=NULL;}
	    }
#endif
	  n=0;
	  if (!(ss->using_schemes)) n = background_zoom_color(args,n,ss);
	  if (last_slider)
	    {
	      XtSetArg(args[n],XmNtopAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
	      XtSetArg(args[n],XmNtopWidget,last_slider); n++;
	      XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
	      XtSetArg(args[n],XmNrightWidget,last_slider); n++;
	    }
	  else
	    {
	      XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
	      XtSetArg(args[n],XmNtopWidget,icon_label); n++;
	      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
	    }
	  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_NONE); n++;
	  XtSetArg(args[n],XmNorientation,XmVERTICAL); n++;
	  XtSetArg(args[n],XmNwidth,15); n++;
	  XtSetArg(args[n],XmNvalue,(int)(vol*100)); n++;
	  XtSetArg(args[n],XmNshowValue,FALSE); n++;
	  wd->wg = XtCreateManagedWidget("mon",xmScaleWidgetClass,p->pane,args,n);
	  last_slider = wd->wg;
	  XtAddCallback(last_slider,XmNvalueChangedCallback,volume_callback,wd);
	  XtAddCallback(last_slider,XmNdragCallback,volume_callback,wd);
	  XtAddCallback(last_slider,XmNhelpCallback,volume_help_callback,wd);
#if HAVE_OSS
      last_device = this_device;
#endif
	}
      gain_ctr += num_audio_gains;
    }

  /* separator between vertical sliders and buttons */
  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_NONE); n++;
  if (num_audio_gains > 0)
    {
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNrightWidget,last_slider); n++;
    }
  else
    {
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
    }
  XtSetArg(args[n],XmNorientation,XmVERTICAL); n++;
  if (vu_meters < 4)
    {XtSetArg(args[n],XmNwidth,10); n++;}
  else {XtSetArg(args[n],XmNwidth,4); n++;}
  XtSetArg(args[n],XmNseparatorType,XmNO_LINE); n++;
  button_vertical_sep = XtCreateManagedWidget("ff-sep5",xmSeparatorWidgetClass,p->pane,args,n);      
  
  /* controls buttons with label */
  n=0;
  if (!(ss->using_schemes)) n = background_high_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,vu_vertical_sep); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNrightWidget,button_vertical_sep); n++;
  if (meter_size<SMALL_FONT_CUTOFF) {XtSetArg(args[n],XmNfontList,small_fontlist); n++;}
  if ((systems == 1) || (!input))
    button_label = XtCreateManagedWidget(Device_Name(device),xmLabelWidgetClass,p->pane,args,n);
  else 
    {
      button1_label = XtCreateManagedWidget(audio_system_name(system),xmLabelWidgetClass,p->pane,args,n);
      /* using 2 labels here because there is no way to get a multiline label (at least in Metroworks Motif) */
      n=0;
      if (!(ss->using_schemes)) n = background_high_color(args,n,ss);
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNtopWidget,button1_label); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNleftWidget,vu_vertical_sep); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNrightWidget,button_vertical_sep); n++;
      if (meter_size<SMALL_FONT_CUTOFF) {XtSetArg(args[n],XmNfontList,small_fontlist); n++;}
      button_label = XtCreateManagedWidget(Device_Name(device),xmLabelWidgetClass,p->pane,args,n);
    }
  
  /* all the buttons and labels except the top device name are contained in a separate box (form widget) */
  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,button_label); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,vu_vertical_sep); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNrightWidget,button_vertical_sep); n++;
  button_box = XtCreateManagedWidget("data",xmFormWidgetClass,p->pane,args,n);
  
  if (vu_meters > 4) 
    button_size = 25;
  else button_size = 100/vu_meters;

  p->on_buttons = (Widget *)calloc(vu_meters,sizeof(Widget));
  p->on_buttons_size = vu_meters;
  
  last_button = NULL;
  last_max = NULL;
  
  for (i=0;i<vu_meters;i++)
    {
      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      if (i<4)
	{XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;}
      else 
	{
	  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNtopWidget,frames[i-4]); n++;
	}
      if (last_button)
	{
	  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
	  XtSetArg(args[n],XmNbottomWidget,last_button); n++;
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNleftWidget,last_button); n++;
	}
      else
	{
	  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
	}
      if (meter_size<SMALL_FONT_CUTOFF) {XtSetArg(args[n],XmNfontList,small_fontlist); n++;}
      if (i<(vu_meters-1))
	{
	  XtSetArg(args[n],XmNrightAttachment,XmATTACH_POSITION); n++;
	  if (i<4)
	    {XtSetArg(args[n],XmNrightPosition,(i+1)*button_size); n++;}
	  else {XtSetArg(args[n],XmNrightPosition,(i-3)*button_size); n++;}
	}
      else 
	{
	  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
	}
      p->on_buttons[i] = XtCreateManagedWidget(channel_name(p,i),xmPushButtonWidgetClass,button_box,args,n);
      last_button = p->on_buttons[i];
      wd = (Wdesc *)calloc(1,sizeof(Wdesc));
      wd->chan = i;
      wd->gain = i + overall_input_ctr;
      wd->ss = ss;
      wd->p = p;
      wd->device = p->device;
      wd->system = system;
      wd->field = AMP_FIELD;
      XtAddCallback(last_button,XmNactivateCallback,Meter_Button_Callback,wd);
      XtAddCallback(last_button,XmNhelpCallback,VU_On_Help_Callback,wd);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNtopWidget,last_button); n++;
      if ((vu_meters<=4) || (i>3))
	{XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;}
      else {XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;}
      if (last_max)
	{
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNleftWidget,last_max); n++;
	}
      else
	{
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
	}
      if (i<(vu_meters-1))
	{
	  XtSetArg(args[n],XmNrightAttachment,XmATTACH_POSITION); n++;
	  if (i<4)
	    {XtSetArg(args[n],XmNrightPosition,button_size*(i+1)); n++;}
	  else {XtSetArg(args[n],XmNrightPosition,button_size*(i-3)); n++;}
	}
      else
	{
	  XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
	}
      XtSetArg(args[n],XmNshadowType,XmSHADOW_ETCHED_OUT); n++;
      last_max = XtCreateManagedWidget("max-frame1",xmFrameWidgetClass,button_box,args,n);
      frames[i] = last_max;
      
      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNrecomputeSize,FALSE); n++;
      if (meter_size<SMALL_FONT_CUTOFF) {XtSetArg(args[n],XmNfontList,small_fontlist); n++;}
      max_label = XtCreateManagedWidget("0.000",xmLabelWidgetClass,last_max,args,n);
      wd = (Wdesc *)calloc(1,sizeof(Wdesc));
      wd->chan = i;
      wd->ss = ss;
      wd->p = p;
      wd->device = p->device;
      wd->system = system;
      wd->field = AMP_FIELD;
      vu = p->meters[i];
      vu->max_button = max_label;
      vu->max_val = 0.0;
      XtAddCallback(max_label,XmNhelpCallback,VU_Max_Help_Callback,wd);

      if ((i==3) && (vu_meters>4)) {last_button = NULL; last_max = NULL;}
    }
  
  if (meter_size<SMALL_FONT_CUTOFF)
    labelstr = XmStringCreate(snd_string_Reset,"small_font");
  else labelstr = XmStringCreate(snd_string_Reset,XmFONTLIST_DEFAULT_TAG);
  n=0;
  if (!(ss->using_schemes)) 
    {
      n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNarmColor,(ss->sgx)->text); n++;
    }
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,button_box); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
  XtSetArg(args[n],XmNbottomWidget,vu_vertical_sep); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNleftWidget,vu_vertical_sep); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNrightWidget,button_vertical_sep); n++;
  if (meter_size<SMALL_FONT_CUTOFF) {XtSetArg(args[n],XmNfontList,small_fontlist); n++;}
  XtSetArg(args[n],XmNlabelString,labelstr); n++;
  p->reset_button = XtCreateManagedWidget("reset",xmPushButtonWidgetClass,p->pane,args,n);
  XtAddCallback(p->reset_button,XmNactivateCallback,VU_Reset_Callback,p);
  XtAddCallback(p->reset_button,XmNhelpCallback,VU_Reset_Help_Callback,ss);
  XmStringFree(labelstr);

  /* now the amp sliders across the bottom of the pane, with 'mixer' info on the right */
  
  last_slider = NULL;
  n=0;
  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
  XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNtopWidget,first_frame); n++;
  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
  XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
  XtSetArg(args[n],XmNrightWidget,button_vertical_sep); n++;
  XtSetArg(args[n],XmNseparatorType,XmNO_LINE); n++;
  XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++;
  XtSetArg(args[n],XmNheight,10); n++;
  slider_sep = XtCreateManagedWidget("amp-sep",xmSeparatorWidgetClass,p->pane,args,n);
  last_slider = slider_sep;
#if 0
  if (all_panes_size > 4) 
    {
      XtVaGetValues(slider_sep,XmNy,&pane_max,NULL);
      p->pane_size = pane_max;
    }
#endif  
  if (input) 
    amp_sliders = p->in_chans * p->out_chans;
  else amp_sliders = p->out_chans;
  p->amps = (AMP **)calloc(amp_sliders,sizeof(AMP *));
  p->amps_size = amp_sliders;
  
  /* input numbering starts at overall_input_ctr and goes up for p->in_chans */
  /* output numbering starts at 0 and goes for p->out_chans */
  /* if input, do direct cases first, then fill in rest of 1, rest of 2 etc */
  temp_out_chan = 0;
  temp_in_chan = 0;
  for (i=0;i<amp_sliders;i++)
    {
      n=0;      
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNalignment,XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNtopWidget,last_slider); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNmarginHeight,CONTROLS_MARGIN); n++;
      XtSetArg(args[n],XmNrecomputeSize,FALSE); n++;
      XtSetArg(args[n],XmNshadowThickness,0); n++;
      XtSetArg(args[n],XmNhighlightThickness,0); n++;
      XtSetArg(args[n],XmNfillOnArm,FALSE); n++;
      
      wd = (Wdesc *)calloc(1,sizeof(Wdesc));
      wd->chan = i;
      wd->ss = ss;
      wd->p = p;
      wd->device = p->device;
      wd->system = system;
      wd->field = AMP_FIELD;

      p->amps[i] = (AMP *)calloc(1,sizeof(AMP));
      a = p->amps[i];
      if (input) 
	{
	  a->type = INPUT_AMP; 
	  a->in = temp_in_chan + overall_input_ctr;
	  a->device_in_chan = temp_in_chan;
	  a->out = temp_out_chan;
	  if (temp_in_chan == temp_out_chan)
	    rec_in_amps[a->in][a->out] = 1.0;
	  else rec_in_amps[a->in][a->out] = 0.0;
	  rec_in_AMPS[a->in][a->out] = p->amps[i];
	  temp_in_chan++;
	  if (temp_in_chan >= p->in_chans)
	    {
	      temp_in_chan = 0;
	      temp_out_chan++;
	    }
	}
      else 
	{
	  a->type = OUTPUT_AMP;
	  a->device_in_chan = 0;
	  a->out = i;
	  rec_out_amps[i] = 1.0;
	  rec_out_AMPS[i] = a;
	}
      
      a->label = XtCreateManagedWidget(gain_channel_name(p,input,a->device_in_chan,a->out),xmPushButtonWidgetClass,p->pane,args,n);
      XtAddCallback(a->label,XmNactivateCallback,Record_Amp_Click_Callback,a);
      XtAddCallback(a->label,XmNhelpCallback,amp_slider_help_Callback,wd);
#if OVERRIDE_TOGGLE
      override_toggle_translation(a->label);
#endif
      
      n=0;
      s1=XmStringCreateLtoR(amp_to_string(global_amp(a)),XmFONTLIST_DEFAULT_TAG);
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNalignment,XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n],XmNtopWidget,a->label); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNleftWidget,a->label); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNlabelString,s1); n++;
      XtSetArg(args[n],XmNmarginHeight,CONTROLS_MARGIN); n++;
      XtSetArg(args[n],XmNrecomputeSize,FALSE); n++;
      a->number = XtCreateManagedWidget ("amp-number",xmLabelWidgetClass,p->pane,args,n);
      XtAddCallback(a->number,XmNhelpCallback,amp_slider_help_Callback,wd);
      XmStringFree(s1);
      
      n=0;      
      if (!(ss->using_schemes)) n = background_scale_color(args,n,ss);
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n],XmNtopWidget,a->number); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNheight,16); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNleftWidget,a->number); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNrightWidget,button_vertical_sep); n++;
      XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++;
      XtSetArg(args[n],XmNmaximum,RECORD_SCROLLBAR_MAX); n++;
      XtSetArg(args[n],XmNvalue,amp_to_slider(global_amp(a))); n++;
      XtSetArg(args[n],XmNdragCallback,make_callback_list(Record_Amp_Drag_Callback,(XtPointer)a)); n++;
      XtSetArg(args[n],XmNvalueChangedCallback,make_callback_list(Record_Amp_ValueChanged_Callback,(XtPointer)a)); n++;
      a->slider = XtCreateManagedWidget("amp",xmScrollBarWidgetClass,p->pane,args,n);
      XtAddCallback(a->slider,XmNhelpCallback,amp_slider_help_Callback,wd);
      last_slider = a->slider;
      XtVaGetValues(a->slider,XmNy,&pane_max,NULL);
    }
  p->in_chan_loc = overall_input_ctr;
  if (input) overall_input_ctr += p->in_chans;
#if HAVE_OSS
  p->pane_size = pane_max+20;
#else
  p->pane_size = pane_max+50;
#endif
  return(p);
}

static int read_adc(snd_state *ss) 
{
  /* assume all in-coming channels are interleaved, (that we know where each is coming from!),
   * rec_amps[...][...] is in this order by first index
   * and second index points to associated output channel
   * value itself is always in-sync with the associated AMP struct's amp field (explicit copy rather than pointer ref)
   * rec_active[...] parallels the rec_amps telling whether the given input is writing to the output
   * rec_in_chans and rec_out_chans guide the array refs
   * vu meter positioning (guided via overall_in_chan field) does not need the max business -- do it direct
   *
   * This is an X-style "work procedure" so it returns true when it is done
   */
  int in_chan,out_chan,i,k,m,n,out_samp,mon_samp,diff,inchn,offset,active_in_chans,cur_size,ochns,sr,sz,ifmt;
  float val;
  if (ever_read == 0) return(1); /* should not happen, but ... */
  out_samp = 0;
  mon_samp = 0;
  ifmt = recorder_in_format(ss);
  ochns = recorder_out_chans(ss);
  sr = recorder_srate(ss);
  sz = recorder_buffer_size(ss);
  if (systems == 1)
    {
      active_in_chans = input_channels[0];
      read_audio(record_fd[0],(char *)record_buf[0],sz*in_datum_size);
      if (ifmt != COMPATIBLE_FORMAT) 
	float_sound((char *)record_buf[0],sz,ifmt,fbuf);
      else for (i=0;i<sz;i++) fbuf[i] = (float)record_buf[0][i];
    }
  else
    {
      active_in_chans = 0;
      /* overall_in_chans is a count of all possible input channels, some of which may be incompatible */
      /* input_channels[i] is how many of these channels can be active at once on a given system */
      for (i=0;i<systems;i++) active_in_chans += input_channels[i];
      offset = 0;
      for (i=0;i<systems;i++)
	{
	  cur_size = sz * input_channels[i] / active_in_chans;
	  read_audio(record_fd[i],(char *)record_buf[i],cur_size*in_datum_size);
	  k=0;
	  if (ifmt != COMPATIBLE_FORMAT) 
	    {
	      if (!ffbuf) ffbuf = (float *)calloc(sz,sizeof(float));
	      float_sound((char *)record_buf[0],cur_size,ifmt,ffbuf);
	      for (m=offset;m<sz;m+=active_in_chans) {for (n=0;n<input_channels[i];n++) fbuf[m+n] = ffbuf[k++];}
	    }
	  else for (m=offset;m<sz;m+=active_in_chans) {for (n=0;n<input_channels[i];n++) fbuf[m+n] = (float)record_buf[i][k++];}
	  offset += input_channels[i];
	}
    }
  for (i=0;i<overall_in_chans;i++) {in_max[i] = 0.0;}
  for (i=0;i<ochns;i++) {out_max[i] = 0.0;}

  /* run through input devices looking for any that are currently turned on */
  /* for each channel currently on, get its associated input channel */

  diff = audio_out_chans - ochns;
  for (i=0;i<sz;i+=active_in_chans)
    {
      for (out_chan=0;out_chan<ochns;out_chan++) {outvals[out_chan] = 0.0;}
      for (in_chan=0;in_chan<overall_in_chans;in_chan++)
	{
	  if (in_device_on[in_chan])
	    {
	      inchn = in_device_chan[in_chan];
	      val = fbuf[i+inchn];
	      if (rec_in_active[in_chan])
		{
		  for (out_chan=0;out_chan<ochns;out_chan++)
		    {
		      outvals[out_chan] += (rec_in_amps[in_chan][out_chan] * val);
		    }
		}
	      if (val<0.0) val=-val; 
	      if (val>in_max[in_chan]) in_max[in_chan]=val;
	    }
	}
      for (out_chan=0;out_chan<ochns;out_chan++)
	{
	  val=outvals[out_chan]*rec_out_amps[out_chan];
	  if ((recording) && (rec_out_active[out_chan]))
	    out_buf[out_samp++] = (short)val;
	  monitor_buf[mon_samp++] = (short)val;
	  if (val<0.0) val=-val;
	  if (val>out_max[out_chan]) out_max[out_chan]=val;
	}
      if (diff>0) for (k=0;k<diff;k++) monitor_buf[mon_samp++] = 0;
    }
  for (in_chan=0;in_chan<overall_in_chans;in_chan++)
    {
      if (in_device_on[in_chan])
	set_vu_val(rec_in_VU[in_chan],in_max[in_chan] * clm_sndflt);
    }
  for (out_chan=0;out_chan<ochns;out_chan++)
    {
      set_vu_val(rec_out_VU[out_chan],out_max[out_chan] * clm_sndflt);
    }

  if (monitor_open)
    {
      write_audio(monitor_fd,(char *)monitor_buf,mon_samp*2);
    }
  if (recording)
    {
      if (data_is_compatible)
	write(output_fd,(char *)out_buf,out_samp*2);
      else 
	{
	  for (i=0,k=0;i<out_samp;i+=ochns,k++)
	    {
	      for (out_chan=0;out_chan<ochns;out_chan++)
		obufs[out_chan][k] = (int)out_buf[i];
	    }
	  clm_write(output_fd,0,(out_samp/ochns)-1,ochns,obufs);
	}
      total_out_samps += out_samp;
      if (total_out_samps > duration_samps)
	{
	  update_duration((float)total_out_samps/(float)(ochns * sr));
	  duration_samps += (sr / 4);
	}
    }
  return(0);
}

static Boolean run_adc(XtPointer ss)  /* X wrapper for read_adc */
{
  return(read_adc((snd_state *)ss));
}

static void set_read_in_progress (snd_state *ss)
{
#if DEBUGGING
  int in_chan,out_chan;
  char *str;
  str = (char *)calloc(512,sizeof(char));
  sprintf(str,"open: srate: %d, format: %s (%s), size: %d, in_chans: %d %d, record_fds: %d %d",
	  recorder_srate(ss),
	  sound_format_name(recorder_in_format(ss)),
	  (data_is_compatible) ? "compatible" : "not compatible",
	  recorder_buffer_size(ss),
	  input_channels[0],input_channels[1],
	  record_fd[0],record_fd[1]);
  record_report(messages,str,NULL);
  for (in_chan=0;in_chan<overall_in_chans;in_chan++)
    {
      sprintf(str,"in[%d, %d] %s (%s): %.3f -> (VU *)%p",
	      in_chan,in_device_chan[in_chan],
	      (in_device_on[in_chan]) ? "on" : "off",
	      (rec_in_active[in_chan]) ? "active" : "idle",
	      in_max[in_chan]*clm_sndflt,
	      rec_in_VU[in_chan]);
      record_report(messages,str,NULL);
    }
  free(str);
#endif
  ever_read = XtAppAddWorkProc(XtWidgetToApplicationContext(main_PANE(ss)),run_adc,(XtPointer)ss);
  /* ever_read will be explicitly stopped if the recorder is closed */
}

static void sensitize_control_buttons(void)
{
  int i;
  for (i=0;i<device_buttons_size-1;i++) /* last button is autoload_file */
    {
      if (device_buttons[i])
	XtSetSensitive(device_buttons[i],TRUE);
    }
}

static void unsensitize_control_buttons(void)
{
  int i;
  for (i=0;i<device_buttons_size-1;i++)
    {
      if (device_buttons[i])
	XtSetSensitive(device_buttons[i],FALSE);
    }
}

void cleanup_recording (void)
{
  if (audio_open) snd_close_audio(0);
  if (recorder) restore_audio_state();
  if ((recorder) && (recording) && (output_fd > 0)) 
    {
      recording = 0;
      sensitize_control_buttons();
      snd_close(output_fd);
    }
}

static void RecordCleanupCB(Widget w,XtPointer clientData,XtPointer callData)
{
  cleanup_recording(); 
}

static void Reset_Record_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* if recording, cancel and toss data, else reset various fields to default (ss) values */
  snd_state *ss = (snd_state *)clientData;
  char *str;
  XmString s1;
  PANE *p;
  VU *vu;
  int i,k;
  if (recording)                  /* cancel */
    {
      recording = 0;
      sensitize_control_buttons();
      XmChangeColor(record_button,(Pixel)(ss->sgx)->main);
      s1 = XmStringCreate(snd_string_Reset,XmFONTLIST_DEFAULT_TAG);
      XtVaSetValues(reset_button,XmNlabelString,s1,NULL);
      XmStringFree(s1);
      s1 = XmStringCreate(snd_string_Record,XmFONTLIST_DEFAULT_TAG);
      XtVaSetValues(record_button,XmNlabelString,s1,NULL);
      XmStringFree(s1);
      snd_close(output_fd);
      output_fd = -1;
      str = just_filename(recorder_file(ss));
      record_report(messages,str," recording cancelled",NULL);
      free(str);
      remove(recorder_file(ss));
    }
  else                            /* reset or restart */
    { 
      for (i=0;i<all_panes_size;i++)
	{
	  p = all_panes[i];
	  for (k=0;k<p->meters_size;k++)
	    {
	      vu = p->meters[k];
	      vu->max_val = 0.0;
	      make_name_label(vu->max_button,"0.00");
	    }
	}
      /* now if dac turned us off, turn everything back on */
      if (!audio_open)            /* restart */
	{
	  fire_up_recorder(ss);
	  s1 = XmStringCreate(snd_string_Reset,XmFONTLIST_DEFAULT_TAG);
	  XtVaSetValues(reset_button,XmNlabelString,s1,NULL);
	  XmStringFree(s1);
	}
    }
}

static void Dismiss_Record_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  XmAnyCallbackStruct *cb = (XmAnyCallbackStruct *)callData;
  state_context *sgx;
  sgx = ss->sgx;
  if (cb->event != sgx->text_activate_event)  /* passed up from textfield widget after <cr> typed */
    {
      if (recording) Reset_Record_Callback(w,clientData,callData);
      XtUnmanageChild(recorder);
      snd_close_audio(0);
      restore_audio_state();
    }
}

static int in_chans_active(void)
{
  PANE *p;
  int val = 0;
  int i,k;
  for (k=0;k<all_panes_size;k++)
    {
      p = all_panes[k];
      if ((p) && (input_device(p->device)))
	{
	  for (i=0;i<p->active_size;i++) {if (p->active[i]) val++;}
	}
    }
  return(val);
}

static int out_chans_active(void)
{
  PANE *p;
  int val = 0;
  int i,k;
  for (k=0;k<all_panes_size;k++)
    {
      p = all_panes[k];
      if ((p) && (!(input_device(p->device))))
	{
	  for (i=0;i<p->active_size;i++) {if (p->active[i]) val++;}
	}
    }
  return(val);
}

static void Record_Button_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  XmString s1 = NULL,s2 = NULL;
  snd_info *sp;
  Wdesc *wd;
  int err,i,comlen,old_srate,ofmt,rs,ochns;
  float duration;
  static char *comment;
  char *str,*str1;
  PANE *p;
  recording = (!recording);
  if (recording)
    {
      if (!audio_open) fire_up_recorder(ss);
      str = XmTextGetString(file_text);
      if ((str) && (*str))
	{
	  str1 = copy_string(str);
	  in_set_recorder_file(ss,copy_string(complete_filename(str1)));
	  free(str1);
	  old_srate = recorder_srate(ss);
	  read_file_data_choices(srate_text,chans_text,
				 header_list,format_list,
				 &rs,&ochns,&out_type,&ofmt); 
	  in_set_recorder_out_format(ss,ofmt);
	  in_set_recorder_out_chans(ss,ochns);
	  if (rs != old_srate) 
	    {
	      in_set_recorder_srate(ss,rs);
	      set_audio_srate(ss,DEFAULT_DEVICE,recorder_srate(ss),0);
	    }
	  data_is_compatible = (recorder_out_format(ss) == COMPATIBLE_FORMAT);
	  out_datum_size = c_snd_datum_size(recorder_out_format(ss));

	  if (recorder_out_chans(ss) <= 0)
	    {
	      record_report(messages,snd_string_cant_record_screwed_up_chans,NULL);
	      recording = 0;
	      return;
	    }
	  comment = XmTextGetString(comment_text);
	  update_duration(0.0);
	  
	  if (out_chans_active() != recorder_out_chans(ss))
	    {
	      sprintf(msgbuf,"chans field (%d) doesn't match file out panel (%d channels active)",recorder_out_chans(ss),out_chans_active());
	      record_report(messages,msgbuf,NULL);
	      wd = (Wdesc *)calloc(1,sizeof(Wdesc));
	      wd->ss = ss;

	      wd->p = all_panes[out_file_pane];
	      p = wd->p;
	      wd->field = AMP_FIELD;
	      wd->device = p->device;
	      wd->system = 0;
	      for (i=0;i<recorder_out_chans(ss);i++)
		{
		  if (!(p->active[i]))
		    {
		      wd->chan = i;
		      Meter_Button_Callback(p->on_buttons[i],(XtPointer)wd,NULL); /* callData not used */
		    }
		}
	      free(wd);
	    }
	  if (in_chans_active() == 0)
	    {
	      record_report(messages,snd_string_cant_record_no_inputs,NULL);
	      recording = 0;
	      return;
	    }
	  XmChangeColor(w,(Pixel)(ss->sgx)->red);
	  s1 = XmStringCreate(snd_string_Cancel,XmFONTLIST_DEFAULT_TAG);
	  s2 = XmStringCreate(snd_string_Done,XmFONTLIST_DEFAULT_TAG);
	  comlen = (int)(snd_strlen(comment) + 3)/4;
	  comlen *= 4;
	  err = snd_write_header(ss,recorder_file(ss),out_type,recorder_srate(ss),recorder_out_chans(ss),28+comlen,0,
				 recorder_out_format(ss),comment,snd_strlen(comment));
	  if (err)
	    {
	      record_report(messages,recorder_file(ss),":\n  ",strerror(errno),"\n  (",snd_error_name(snd_IO_error),")",NULL);
	      recording = 0;
	      return;
	    }

	  unsensitize_control_buttons();

	  output_fd = snd_reopen_write(ss,recorder_file(ss));
	  c_read_header_with_fd(output_fd);
	  open_clm_file_descriptors(output_fd,recorder_out_format(ss),out_datum_size,c_snd_header_data_location());
	  total_out_samps = 0;
	  duration_samps = recorder_srate(ss)/4;
	  if (!data_is_compatible)
	    {
	      if (!obufs)
		obufs = (int **)calloc(2,sizeof(int *));
	      for (i=0;i<recorder_out_chans(ss);i++) 
		{
		  if (!obufs[i])
		    obufs[i] = (int *)snd_calloc(ss,recorder_buffer_size(ss),sizeof(int));
		}
	    }
	}
      else
	{
	  record_report(messages,snd_string_cant_record_no_output_file,NULL);
	  recording = 0;
	  return;
	}
    }
  else 
    {
      /* total_out_samps -> true duration here for header update */
      sensitize_control_buttons();
      XmChangeColor(w,(Pixel)(ss->sgx)->main);
      s1 = XmStringCreate(snd_string_Reset,XmFONTLIST_DEFAULT_TAG);
      s2 = XmStringCreate(snd_string_Record,XmFONTLIST_DEFAULT_TAG);
      snd_close(output_fd);
      output_fd = clm_reopen_write(recorder_file(ss));
      c_update_header_with_fd(output_fd,out_type,total_out_samps*out_datum_size); 
      close(output_fd);
      output_fd = -1;
      duration = (float)total_out_samps / (float)(recorder_out_chans(ss) * recorder_srate(ss));
      update_duration(duration);
      str = (char *)calloc(256,sizeof(char));
      sprintf(str,"recorded %s:\n  duration: %.2f\n  srate: %d, chans: %d\n  type: %s, format: %s",
	      recorder_file(ss),duration,recorder_srate(ss),recorder_out_chans(ss),
	      sound_type_name(out_type),sound_format_name(recorder_out_format(ss)));
      record_report(messages,str,NULL);
      free(str);
      if (recorder_autoload(ss))
	{
	  if ((sp = find_sound(ss,recorder_file(ss))))
	    snd_update(ss,sp);
	  else snd_open_file(recorder_file(ss),ss);
	}
    }
  if (s1)
    {
      XtVaSetValues(reset_button,XmNlabelString,s1,NULL);
      XmStringFree(s1);
    }
  if (s2)
    {
      XtVaSetValues(record_button,XmNlabelString,s2,NULL);
      XmStringFree(s2);
    }
}

static void initialize_recorder(void);
static Widget rec_panes,message_pane,file_info_pane;

void snd_record_file(snd_state *ss)
{
  Arg args[32];
  int n,i,k,device,all_devices,input_devices,output_devices,def_out,system,cur_devices;
  int *ordered_devices,*ordered_systems;
  XmString xdismiss,xhelp,xreset,titlestr;
  XFontStruct *small_fontstruct;
  float audval[64];
  Atom wm_delete;
  PANE *p;

  if (!recorder)
    {
      systems = audio_systems();
      input_devices = 0;
      output_devices = 0;
      all_devices = 0;
#if HAVE_OSS
      read_mixer_state(MIXER_SAVE_FILE);
#endif
      for (system=0;system<systems;system++)
	{
#if HAVE_OSS
	  mixer_gains_posted[system] = 0;
	  tone_controls_posted[system] = 0;
#endif
	  /* look for audio input devices -- if none, report problem and quit */
	  read_audio_state(AUDIO_SYSTEM(system) | DEFAULT_DEVICE,DEVICE_FIELD,64,audval);
	  cur_devices = audval[0];
	  if (cur_devices == 0) {snd_printf(ss,"no audio devices available"); return;}
	  for (i=0;i<cur_devices;i++) 
	    {
	      device = (int)audval[i+1];
	      if (input_device(device))
		input_devices++;
	      else 
		{
		  if ((system == 0) && (output_device(device)))
		    output_devices++;
		}
	      audio_gains_size += device_gains(AUDIO_SYSTEM(system) | device);
	    }
	  all_devices += cur_devices;
	}
      if (input_devices == 0) {snd_printf(ss,"no audio input devices available"); return;}
      all_panes_size = input_devices + 1;  /* one for all output devices -- maybe there should be more */
      ordered_devices = (int *)calloc(all_panes_size,sizeof(int));
      ordered_systems = (int *)calloc(all_panes_size,sizeof(int));
      all_panes = (PANE **)calloc(all_panes_size,sizeof(PANE *));
      device_buttons_size = input_devices + 2; /* inputs, one output, autoload_file */
      device_buttons = (Widget *)calloc(device_buttons_size,sizeof(Widget));
      audio_gains = (float *)calloc(audio_gains_size,sizeof(float));
      audio_GAINS = (Wdesc **)calloc(audio_gains_size,sizeof(Wdesc *));
      /* out_file_pane will be the bottom (output) audio pane, not the file info pane */
      overall_in_chans = 0;
      def_out = 2;
      k=0;
      for (system=0;system<systems;system++)
	{
	  read_audio_state(AUDIO_SYSTEM(system) | DEFAULT_DEVICE,DEVICE_FIELD,64,audval);
	  cur_devices = audval[0];
	  for (i=0;i<cur_devices;i++)
	    {
	      device = (int)audval[i+1];
	      if (input_device(device))
		{
		  n = device_channels(AUDIO_SYSTEM(system) | device);
		  overall_in_chans += n;
		  if (n > def_out) def_out = n;
		  ordered_devices[k] = device;
		  ordered_systems[k] = system;
		  if (device == DIGITAL_IN_DEVICE) digital_in_button = k;
		  else if (device == MICROPHONE_DEVICE) microphone_button = k;
		  else if (device == LINE_IN_DEVICE) line_in_button = k;
		  k++;
		}
	    }
	}
      if (output_devices) ordered_devices[k] = DAC_OUT_DEVICE;
      rec_in_active = (int *)calloc(overall_in_chans,sizeof(int));
      rec_out_active = (int *)calloc(MAX_OUT_CHANS,sizeof(int));
      rec_in_VU = (VU **)calloc(overall_in_chans,sizeof(VU *));
      rec_out_VU = (VU **)calloc(MAX_OUT_CHANS,sizeof(VU *));
      outvals = (float *)calloc(MAX_OUT_CHANS,sizeof(float));
      out_max = (float *)calloc(MAX_OUT_CHANS,sizeof(float));
      in_max = (float *)calloc(overall_in_chans,sizeof(float));
      in_device_on = (int *)calloc(overall_in_chans,sizeof(int));
      in_device_chan = (int *)calloc(overall_in_chans,sizeof(int));
      rec_in_amps = (float **)calloc(overall_in_chans,sizeof(float *));
      rec_in_AMPS = (AMP ***)calloc(overall_in_chans,sizeof(AMP **));
      for (i=0;i<overall_in_chans;i++) 
	{
	  rec_in_amps[i] = (float *)calloc(MAX_OUT_CHANS,sizeof(float));
	  rec_in_AMPS[i] = (AMP **)calloc(MAX_OUT_CHANS,sizeof(AMP *));
	}
      rec_out_amps = (float *)calloc(MAX_OUT_CHANS,sizeof(float));  /* monitor (speaker) vol, not file-output */
      rec_out_AMPS = (AMP **)calloc(MAX_OUT_CHANS,sizeof(AMP *));
      /* out chans defaults to def_out = parallel to the maximal input choice or 2 whichever is more */
      if (def_out < 2) def_out = 2;

      /* now create recording dialog using the info gathered above */
      small_fontstruct = XLoadQueryFont(main_DISPLAY(ss),(vu_size(ss) < SMALLER_FONT_CUTOFF) ? SMALLER_FONT : SMALL_FONT);
      if (small_fontstruct)
	{
	  small_fontlist = XmFontListCreate(small_fontstruct,"smallfont");
	  XmFontListEntryCreate("small_font",XmFONT_IS_FONT,small_fontstruct);
	}
      else
	{
	  small_fontlist = button_FONT(ss);
	  XmFontListEntryCreate("small_font",XmFONT_IS_FONT,peak_numbers_FONTSTRUCT(ss));
	}
      xdismiss = XmStringCreate(snd_string_Dismiss,XmFONTLIST_DEFAULT_TAG);
      xhelp = XmStringCreate(snd_string_Help,XmFONTLIST_DEFAULT_TAG);
      xreset = XmStringCreate(snd_string_Reset,XmFONTLIST_DEFAULT_TAG);
      titlestr = XmStringCreateLocalized(snd_string_Record);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNcancelLabelString,xreset); n++;
      XtSetArg(args[n],XmNhelpLabelString,xhelp); n++;
      XtSetArg(args[n],XmNokLabelString,xdismiss); n++;
      XtSetArg(args[n],XmNautoUnmanage,FALSE); n++;
      XtSetArg(args[n],XmNdialogTitle,titlestr); n++;
#if RESIZE_DIALOG
      XtSetArg(args[n],XmNallowResize,TRUE); n++;
      XtSetArg(args[n],XmNresizePolicy,XmRESIZE_ANY); n++;
      XtSetArg(args[n],XmNnoResize,FALSE); n++;
#endif
#if UNSET_TRANSIENT
      XtSetArg(args[n],XmNtransient,FALSE); n++;
#endif
      recorder = XmCreateTemplateDialog(main_SHELL(ss),snd_string_Record,args,n);
#if UNSET_TRANSIENT
      add_dialog(ss,recorder);
#endif
      XtAddCallback(recorder,XmNcancelCallback,Reset_Record_Callback,ss);
      XtAddCallback(recorder,XmNhelpCallback,Help_Record_Callback,ss);
      XtAddCallback(recorder,XmNokCallback,Dismiss_Record_Callback,ss);
      XmStringFree(xhelp);
      XmStringFree(xdismiss);
      XmStringFree(xreset);

#ifndef LESSTIF_VERSION
      /* now add the RECORD button to the others at the bottom of the window */
      /* this is a push button from Motif's point of view, but its behaviour is like a toggle button */
      /* in Lesstif, this causes a segmentation fault, so we move the record button elsewhere */
      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNlabelString,titlestr); n++;
      record_button = XtCreateManagedWidget("record-button",xmPushButtonWidgetClass,recorder,args,n);
      XtAddCallback(record_button,XmNactivateCallback,Record_Button_Callback,ss);
#endif
      XmStringFree(titlestr);

#ifndef LESSTIF_VERSION
      reset_button = XtNameToWidget(recorder,"Cancel");
#else
      reset_button = XmMessageBoxGetChild(recorder,XmDIALOG_CANCEL_BUTTON);
#endif

      if (!(ss->using_schemes))
	{
	  XtVaSetValues(XmMessageBoxGetChild(recorder,XmDIALOG_CANCEL_BUTTON),XmNarmColor,(ss->sgx)->text,NULL);
	  XtVaSetValues(XmMessageBoxGetChild(recorder,XmDIALOG_HELP_BUTTON),XmNarmColor,(ss->sgx)->text,NULL);
	  XtVaSetValues(XmMessageBoxGetChild(recorder,XmDIALOG_OK_BUTTON),XmNarmColor,(ss->sgx)->text,NULL);
	}

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
#ifdef LESSTIF_VERSION
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
#else
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNbottomWidget,XmMessageBoxGetChild(recorder,XmDIALOG_SEPARATOR)); n++;
#endif
      XtSetArg(args[n],XmNallowResize,TRUE); n++;
      XtSetArg(args[n],XmNsashHeight,10); n++;
      XtSetArg(args[n],XmNsashWidth,10); n++;
      XtSetArg(args[n],XmNsashIndent,-1); n++; /* need room for icons -- default is -10 */
#if (!(PANED_WINDOWS_BUGGY))
      rec_panes = XtCreateManagedWidget("rec-panes",xmPanedWindowWidgetClass,recorder,args,n);
#else
      rec_panes = XtCreateManagedWidget("rec-panes",xmRowColumnWidgetClass,recorder,args,n);
#endif

      XtManageChild(recorder);
      make_record_icons(recorder,ss);

      /* open all audio devices and collect ins/out etc */
      out_file_pane = (all_panes_size - 1);
      for (i=0;i<all_panes_size;i++)
	{
	  /* last one is output */
	  device = ordered_devices[i];
	  system = ordered_systems[i];
	  all_panes[i] = make_pane(ss,rec_panes,device,system);
	}

      /* then make file_info_pane and message_pane at the bottom */
      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNallowResize,TRUE); n++;

      file_info_pane = XtCreateManagedWidget("file-pane",xmFormWidgetClass,rec_panes,args,n);
      message_pane = XtCreateManagedWidget("msg-pane",xmFormWidgetClass,rec_panes,args,n);

      make_file_info_pane(ss,file_info_pane,ordered_devices,all_panes_size,ordered_systems);
      messages = make_message_pane(ss,message_pane);
      if (!(ss->using_schemes)) map_over_children(rec_panes,color_sashes,(void *)ss);

      /* loop through all panes reading p->pane_size and */
      for (i=0;i<all_panes_size;i++)
	{
	  p = all_panes[i];
	  XtVaSetValues(p->pane,XmNpaneMaximum,p->pane_size,NULL);
#ifdef LESSTIF_VERSION
	  XtVaSetValues(p->pane,XmNheight,p->pane_size,NULL);
#endif
	}

#ifdef LESSTIF_VERSION
      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      record_button = XtCreateManagedWidget(snd_string_Record,xmPushButtonWidgetClass,recorder,args,n);
      XtAddCallback(record_button,XmNactivateCallback,Record_Button_Callback,ss);
#endif

      /* in case caller closes (via window menu) dialog: */
      wm_delete = XmInternAtom(XtDisplay(recorder),"WM_DELETE_WINDOW",FALSE);
      XmAddWMProtocolCallback(XtParent(recorder),wm_delete,RecordCleanupCB,(XtPointer)ss);

      initialize_recorder();
    }
  else 
    {
      raise_dialog(recorder);
    }
  if (!XtIsManaged(recorder)) XtManageChild(recorder);
  XtVaSetValues(message_pane,XmNpaneMinimum,1,NULL);
  for (i=0;i<all_panes_size;i++)
    {
      p = all_panes[i];
      XtVaSetValues(p->pane,XmNpaneMaximum,1000,NULL); /* release max once we're set up so user can do what he wants */
    }

  if (pending_errors>0)
    {
      for (i=0;i<pending_errors;i++)
	{
	  record_report(messages,pending_error[i],NULL);
	  free(pending_error[i]);
	}
      pending_errors = 0;
      free(pending_error);
      pending_error_size = 0;
    }

  if (!audio_open) fire_up_recorder(ss);
}

typedef struct {
  int which,vali,valj;
  float valf;
} recorder_setf;

static recorder_setf **setfs = NULL;
static int setfs_size = 0;
static int current_setf = 0;

static void add_pending_setf(int which, int vali, int valj, float valf)
{
  if (current_setf >= setfs_size)
    {
      setfs_size += 16;
      if (setfs) 
	setfs = (recorder_setf **)realloc(setfs,setfs_size * sizeof(recorder_setf *));
      else
	setfs = (recorder_setf **)calloc(setfs_size,sizeof(recorder_setf *));
    }
  setfs[current_setf] = (recorder_setf *)calloc(1,sizeof(recorder_setf));
  setfs[current_setf]->which = which;
  setfs[current_setf]->vali = vali;
  setfs[current_setf]->valj = valj;
  setfs[current_setf]->valf = valf;
  current_setf++;
}

static float scan_pending_setfs(int which, int vali, int valj)
{
  int i;
  float val;
  recorder_setf *rs;
  val = 0.0;
  for (i=0;i<current_setf;i++)
    {
      rs = setfs[i];
      if (which == rs->which)
	{
	  switch (which)
	    {
	    case REC_IN_AMPS: if ((vali == rs->vali) && (valj == rs->valj)) val = rs->valf; break;
	    case REC_OUT_AMPS:        
	    case AUDIO_GAINS: if (vali == rs->vali) val = rs->valf; break;
	    }
	}
    }
  return(val);
}

void set_autoload(snd_state *ss, int val)
{
  in_set_recorder_autoload(ss,val);
  if (recorder) XmToggleButtonSetState(device_buttons[autoload_button],val,FALSE); 
}

float read_record_state(int which, int vali, int valj)
{
  if (!recorder) /* check list of pending setfs */
    return(scan_pending_setfs(which,vali,valj));
  else
    {
      switch (which)
	{
	case REC_IN_AMPS:  return(rec_in_amps[vali][valj]); break;
	case REC_OUT_AMPS: return(rec_out_amps[vali]); break;
	case AUDIO_GAINS:  return(audio_gains[vali]); break;
	}
    }
  return(0.0);
}

void write_record_state(int which, int vali, int valj, float valf)
{
  /* snd-clm input port-hole */
  int temp;
  Wdesc *wd;
  switch (which)
    {
    case REC_IN_AMPS:     
      if (recorder)
	{
	  temp = amp_to_slider(valf); 
	  record_amp_changed(rec_in_AMPS[vali][valj],temp); 
	  XtVaSetValues(rec_in_AMPS[vali][valj]->slider,XmNvalue,temp,NULL); 
	}
      else add_pending_setf(which,vali,valj,valf);
      break;
    case REC_OUT_AMPS:
      if (recorder)
	{
	  temp = amp_to_slider(valf); 
	  record_amp_changed(rec_out_AMPS[vali],temp); 
	  XtVaSetValues(rec_out_AMPS[vali]->slider,XmNvalue,temp,NULL); 
	}
      else add_pending_setf(which,vali,valj,valf);
      break;
    case AUDIO_GAINS:
      if (recorder)
	{
	  wd = audio_GAINS[vali];
	  XtVaSetValues(wd->wg,XmNvalue,(int)(valf * 100),NULL);
	  set_audio_gain(wd,valf);
	}
      else add_pending_setf(which,vali,valj,valf);
      break;
    }
}

static void initialize_recorder(void)
{
  /* picked up initial (widget) values from globals vars */
  int i;
  /* special case initializations */
#if OLD_SGI_AL
  /* in this case, digital in blocks out the others */
  long sb[2];
  int err;
  int in_digital = 0;
  sb[0] = AL_INPUT_SOURCE;
  err = ALgetparams(AL_DEFAULT_DEVICE,sb,2);
  if ((!err) && (sb[1] == AL_INPUT_DIGITAL)) in_digital = 1;
  XmToggleButtonSetState(device_buttons[digital_in_button],in_digital,FALSE);
  device_button_callback(device_buttons[digital_in_button],all_panes[digital_in_button],NULL);
#endif
#if NEW_SGI_AL
  /* for simplicity, and until someone complains, new SGI AL machines will just have one active input device */
  active_device_button = microphone_button;
  for (i=0;i<device_buttons_size-1;i++)
    {
      if (device_buttons[i])
	{
	  if ((i != microphone_button) && (input_device(all_panes[i]->device)))
	    {
	      XmToggleButtonSetState(device_buttons[i],FALSE,TRUE); 
	    }
	}
    }
#endif
  for (i=0;i<current_setf;i++)
    write_record_state(setfs[i]->which,setfs[i]->vali,setfs[i]->valj,setfs[i]->valf);
}

static int fire_up_recorder(snd_state *ss)
{
  int i;
#if NEW_SGI_AL
  int j,n;
  PANE *p;
#endif
#ifdef SGI
  float val[1];
  int err,cur_dev,new_srate = 0;
  char sbuf[8];
  long sb[8];
#endif
  for (i=0;i<systems;i++)
    if (!(record_buf[i]))
      record_buf[i] = (short *)snd_calloc(ss,recorder_buffer_size(ss),sizeof(short));
  if (!monitor_buf) monitor_buf = (short *)snd_calloc(ss,recorder_buffer_size(ss),sizeof(short));
  if (!out_buf) out_buf = (short *)snd_calloc(ss,recorder_buffer_size(ss),sizeof(short));
  if (!fbuf) fbuf = (float *)snd_calloc(ss,recorder_buffer_size(ss),sizeof(float));
  for (i=0;i<systems;i++) record_fd[i] = -1;
#if HAVE_OSS
  if (settings_saved) restore_audio_state(); else save_audio_state();
#else
  save_audio_state();
#endif

#ifdef SGI
  cur_dev = MICROPHONE_DEVICE;
  #if OLD_SGI_AL
    sb[0] = AL_INPUT_SOURCE;
    err = ALgetparams(AL_DEFAULT_DEVICE,sb,2);
    if (!err)
      {
	if (sb[1] == AL_INPUT_LINE)
	  cur_dev = LINE_IN_DEVICE;
	else
	  if (sb[1] == AL_INPUT_DIGITAL)
	    cur_dev = DIGITAL_IN_DEVICE;
	if (cur_dev == DIGITAL_IN_DEVICE)
	  sb[0] = AL_DIGITAL_INPUT_RATE;
	else sb[0] = AL_INPUT_RATE;
	err = ALgetparams(AL_DEFAULT_DEVICE,sb,2);
	if (!err)
	  new_srate = sb[1];
	if (cur_dev == DIGITAL_IN_DEVICE) 
	  input_channels[0] = 2;
	else input_channels[0] = 4;
      }
    else
      {
	err = read_audio_state(AUDIO_SYSTEM(0) | MICROPHONE_DEVICE,SRATE_FIELD,0,val);
	if (!err) in_set_recorder_srate(ss,(int)val[0]);
      }
    if ((new_srate > 0) && (new_srate != recorder_srate(ss))) 
      {
	in_set_recorder_srate(ss,new_srate);
	sprintf(sbuf,"%d",recorder_srate(ss));
	XmTextSetString(srate_text,sbuf);
      }
    audio_out_chans = input_channels[0];
    for (i=0;i<4;i++) 
      {
        in_device_on[i] = (cur_dev != DIGITAL_IN_DEVICE);
	in_device_chan[i] = i;
      }
    in_device_on[4] = (cur_dev == DIGITAL_IN_DEVICE);
    in_device_chan[4] = 0; 
    in_device_on[5] = (cur_dev == DIGITAL_IN_DEVICE);
    in_device_chan[5]=1;
    if (cur_dev != DIGITAL_IN_DEVICE) write_audio_state(AUDIO_SYSTEM(0) | MICROPHONE_DEVICE,CHANNEL_FIELD,2,NULL);
  #endif
  #if NEW_SGI_AL
    err = read_audio_state(AUDIO_SYSTEM(0) | MICROPHONE_DEVICE,SRATE_FIELD,0,val);
    new_srate = (int)val[0];
    if (!err) 
      {
	if ((new_srate > 0) && (recorder_srate(ss) != new_srate))
	  {
	    in_set_recorder_srate(ss,new_srate);
	    sprintf(sbuf,"%d",recorder_srate(ss));
	    XmTextSetString(srate_text,sbuf);
	  }
      }
    audio_out_chans = 2;
    for (i=0;i<all_panes_size;i++)
      {
	p = all_panes[i];
	if (input_device(p->device))
	  {
	    for (j=p->in_chan_loc,n=0;n<p->in_chans;n++,j++) 
	      {
		in_device_on[j]=(p->device == MICROPHONE_DEVICE);
		in_device_chan[j] = n;
	      }
	  }
      }
  #endif
#else /* not SGI */
  if (recorder_srate(ss) <= 0) in_set_recorder_srate(ss,22050);
  audio_out_chans = 2;
  for (i=0;i<overall_in_chans;i++) 
    {
      in_device_on[i] = 1; 
      in_device_chan[i] = i;
    }
#endif

  for (i=0;i<systems;i++)
    {
      if (input_channels[i] == 0)
	{
#if OLD_SGI_AL
	  input_channels[i] = ((cur_dev == DIGITAL_IN_DEVICE) ? 2 : 4);
	  audio_out_chans = input_channels[i];
#else
	  input_channels[i] = 2;
#endif
	}
    }

#if NEW_SGI_AL
  record_fd[0] = open_audio_input(AUDIO_SYSTEM(0) | MICROPHONE_DEVICE,recorder_srate(ss),input_channels[0],
				  recorder_in_format(ss),recorder_buffer_size(ss));
#else
  #if OLD_SGI_AL
    record_fd[0] = open_audio_input(AUDIO_SYSTEM(0) | cur_dev,recorder_srate(ss),input_channels[0],
				    recorder_in_format(ss),recorder_buffer_size(ss));
  #else
    for (i=0;i<systems;i++)
      {
	record_fd[i] = open_audio_input(AUDIO_SYSTEM(i) | READ_WRITE_DEVICE,recorder_srate(ss),input_channels[i],
					recorder_in_format(ss),recorder_buffer_size(ss));
	if (record_fd[i] == -1) /* perhaps not full-duplex */
	  {
	    record_fd[i] = open_audio_input(AUDIO_SYSTEM(i) | DEFAULT_DEVICE,recorder_srate(ss),input_channels[i],
					    recorder_in_format(ss),recorder_buffer_size(ss));
	  }
      }
  #endif
#endif
  if (record_fd[0] == -1)
    {
      record_report(messages,"open device: ",audio_error_name(audio_error()),NULL);
      return(-1);
    }
  audio_open = 1;
#if HAVE_OSS
  monitor_fd = 0;

  /*
   * if (full_duplex(0))
   *   monitor_fd = open_audio_output(READ_WRITE_DEVICE,recorder_srate(ss),audio_out_chans,recorder_out_format(ss),recorder_buffer_size(ss));
   * else record_report(messages,"Simultaneous input and output is not enabled on this card.",NULL);
   */

#else
  monitor_fd = open_audio_output(AUDIO_SYSTEM(0) | DAC_OUT_DEVICE,recorder_srate(ss),audio_out_chans,
				 recorder_out_format(ss),recorder_buffer_size(ss));
#endif
  if (monitor_fd == -1)
    {
      record_report(messages,"open output: ",audio_error_name(audio_error()),NULL);
      monitor_open = 0;
    }
#if HAVE_OSS
  else monitor_open = monitor_fd;
#else
  else monitor_open = 1;
#endif
  set_read_in_progress(ss);
  return(0);
}

static void set_record_size (snd_state *ss, int new_size)
{
  int i;
  lock_recording_audio();
  if (new_size > recorder_buffer_size(ss))
    {
      in_set_recorder_buffer_size(ss,new_size);
      if (ffbuf)
	{
	  free(ffbuf);
	  ffbuf = (float *)snd_calloc(ss,new_size,sizeof(float));
	}
      if (obufs)
	{
	  for (i=0;i<recorder_out_chans(ss);i++) 
	    {
	      if (obufs[i]) 
		{
		  free(obufs[i]);
		  obufs[i] = (int *)snd_calloc(ss,new_size,sizeof(int));
		}
	    }
	}
      for (i=0;i<systems;i++)
	{
	  if (record_buf[i]) 
	    {
	      free(record_buf[i]);
	      record_buf[i] = (short *)snd_calloc(ss,new_size,sizeof(short));
	    }
	}
      if (monitor_buf) 
	{
	  free(monitor_buf);
	  monitor_buf = (short *)snd_calloc(ss,new_size,sizeof(short));
	}
      if (out_buf) 
	{
	  free(out_buf);
	  out_buf = (short *)snd_calloc(ss,new_size,sizeof(short));
	}
      if (fbuf) 
	{
	  free(fbuf);
	  fbuf = (float *)snd_calloc(ss,new_size,sizeof(float));
	}
    }
  else in_set_recorder_buffer_size(ss,new_size);
  if ((recorder) && (rec_size_text)) 
    {
      sprintf(timbuf,"%d",recorder_buffer_size(ss));
      XmTextSetString(rec_size_text,timbuf);
    }
  unlock_recording_audio();
}

int record_dialog_is_active(void)
{
  return((recorder) && (XtIsManaged(recorder)));
}


#if 0
/* to ensure alReadBuffers: -- this might be handy when multiple active inputs are supported */
  pv.param = AL_VERSION; alGetParams(AL_SYSTEM,&pv,1); if (pv.sizeOut < 0 || pv.value.i < 6) {/* feature not present */}
  ALport p;
  short buf[8][1000];
  void *bufs[8];
  int i,j;
  ALconfig c;
  c = alNewConfig();
  alSetChannels(c, 8);
  p = alOpenPort("alReadBuffers example","r",c);
  for (i = 0; i < 8; i++) {bufs[i] = buf[i];}
  alReadBuffers(p, bufs, 0, 1000);          /* read 1000 8-channel frames */
#endif


