/* $TOG: piano.c /main/11 1997/05/14 13:42:25 bill $ */
/*
 * Motif
 *
 * Copyright (c) 1987-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these librararies and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 * 
 */
/*
 * HISTORY
 */
/****************************************************************************
 ****************************************************************************
 **
 **   File:         piano.c
 **
 **   Version:      2.0
 **
 **   By:           Andrew deBlois
 **
 **   This application won't be able to play tunes on the pmax and sun
 **   since you can't change the tones generated by XBell.
 **
 ****************************************************************************
 ****************************************************************************/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <Xm/XmP.h>
#include <Xm/Xm.h>
#include <X11/Shell.h>
#include <Xm/BulletinB.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingAP.h>
#include <Xm/DrawingA.h>
#include <Xm/FileSB.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/MainW.h>
#include <Xm/MenuShell.h>
#include <Xm/MessageB.h>
#include <Xm/PanedW.h>
#include <Xm/PushBP.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/Scale.h>
#include <Xm/ScrolledW.h>
#include <Xm/SelectioB.h>
#include <Xm/Separator.h>
#include <Xm/XpmP.h>
#include "piano.images"


/* note that anything after REST must be some type of rest. */

typedef enum {EIGHTH,  EIGHTHDOT,  EIGHTHSHARP,  EIGHTHDOTSHARP,
	      QUARTER, QUARTERDOT, QUARTERSHARP, QUARTERDOTSHARP,
	      HALF,    HALFDOT,    HALFSHARP,    HALFDOTSHARP,
	      REST,    RESTDOT,    LAST_NOTE} NoteType;

char *noteName[] = {"eighth",  "eighthdot",  "eighthsharp",  "eighthdotsharp",
                    "quarter", "quarterdot", "quartersharp", "quarterdotsharp",
                    "half",    "halfdot",    "halfsharp",    "halfdotsharp",
                    "rest",    "restdot"};

typedef enum { MENU_QUIT, MENU_HELP } MenuFunction;


/*-------------------------------------------------------------*
 |		               Types			       |
 *-------------------------------------------------------------*/


/*****
 * NoteDescription:	Structure to hold the description of all note types supported.
 */
typedef struct _NoteDescription
{
   Pixmap		image;
   Pixmap		mask;
} NoteDescription;


/*****
 * NoteRec:		Data stored in each note after it is added to the staff.
 */
typedef struct _NoteRec
{
   Display	       *display;
   NoteType		noteType;       /* type of note or rest. */
   int			noteDuration;
   int			noteNumber;     /* number of note to play */
   int			noteIndex;      /* index used for positioning. */
   int			ledgerLine;
   struct _NoteRec *next;
} NoteRec;


/*****
 * StaffRec:		Data used to manipulate the staff containing a list of notes.
 */
typedef struct _StaffRec
{
   Display		*display;
   Widget		 staff;
   NoteRec		*notes;
   Widget		divider;	/* seperator above this staff. */
   struct _StaffRec	*prev, *next;
} StaffRec;


/*****
 * AppData:		Data used throughout the app to hold all necessary data.
 */
typedef struct _AppData
{
   /* resources */
   int			baseDuration; 	/* equal to one quarter note */
   float		baseFrequency;	/* frequency assigned to new staffs. */
   Boolean		useKeyboard;	/* if true, keyboard played with voice(s). */
   int			wkeyCount;	/* number of white keys on the keyboard. */
   int			keyHeight;	/* white key keight - also sets black keys */
   int			keyWidth;	/* width of each black key. */

   /* data */
   GC			noteGC;
   NoteType		activeNoteType;		/* index into the noteTable */
   NoteDescription	noteTable[LAST_NOTE];	/* definition info for each note type. */
   StaffRec            *staffList;              /* holds list of all staffs (voices). */
   Widget               score;                  /* the rowcolumn holding the staffs. */
} AppData;


/*-------------------------------------------------------------*
 |		              defines			       |
 *-------------------------------------------------------------*/

#define APP_NAME  "piano"
#define APP_CLASS "Piano"

/*
 * default resource settings.
 */
#define DEFAULT_BASE_FREQUENCY	"246.9413"
#define DEFAULT_BASE_DURATION	200
#define DEFAULT_WKEY_COUNT	28            /* number of white keys */
#define DEFAULT_KEY_HEIGHT	160
#define DEFAULT_KEY_WIDTH	20

#define LOCAL_NAME		"local"       /* just used for a label */
  
#define EMSG1 "Fatal Error -- Cannot allocate memory for resources.\n"


/*-------------------------------------------------------------*
 |		            Resources			       |
 *-------------------------------------------------------------*/

XtResource appRes[] =
{
  {"baseDuration", "BaseDuration", XtRInt, sizeof(int),
     XtOffsetOf(AppData, baseDuration), XtRImmediate,
     (XtPointer)DEFAULT_BASE_DURATION},
  
  {"baseFrequency", "BaseFrequency", XtRFloat, sizeof(float),
     XtOffsetOf(AppData, baseFrequency), XmRString,
     DEFAULT_BASE_FREQUENCY},
  
  {"useKeyboard", "UseKeyboard", XtRBoolean, sizeof(Boolean),
     XtOffsetOf(AppData, useKeyboard), XmRImmediate,
     (XtPointer)TRUE},
  
  {"wkeyCount", "WkeyCount", XtRInt, sizeof(int),
     XtOffsetOf(AppData, wkeyCount), XtRImmediate,
     (XtPointer)DEFAULT_WKEY_COUNT},
  
  {"keyHeight", "KeyHeight", XtRInt, sizeof(int),
     XtOffsetOf(AppData, keyHeight), XtRImmediate,
     (XtPointer)DEFAULT_KEY_HEIGHT},
  
  {"keyWidth", "KeyWidth", XtRInt, sizeof(int),
     XtOffsetOf(AppData, keyWidth), XtRImmediate,
     (XtPointer)DEFAULT_KEY_WIDTH},
};



/*-------------------------------------------------------------*
 |		      	Function Declarations     	       |
 *-------------------------------------------------------------*/

/* KEYBOARD */
void   BuildKeys         (Widget);
Widget CreateKeyboard    (Widget);

/* SCORE */
StaffRec *GetStaffData      (Widget);
void      DrawNotes         (Widget, int, int);
void      DrawStaffCB       (Widget, XtPointer, XtPointer);
void      SetIcon           (Widget, Pixmap);
void      DrawNote          (Widget, NoteRec *, int, int);
void      SetActiveNote     (Widget, NoteType);
void      AddNoteAtPosn     (Widget, int, NoteType);
void      AddNoteToStaffCB  (Widget, XtPointer, XtPointer);
void      AddNewStaff       (Display *, char *);
void      PostStaffMenu     (Widget, XtPointer, XEvent *, Boolean *);
void	  CreateStaffMenu   (Widget, Widget, char *);

/* OTHER */
void   AddVoiceCB        (Widget, XtPointer, XtPointer);
void   RemoveVoiceCB     (Widget, XtPointer, XtPointer);
void   ClearVoiceCB      (Widget, XtPointer, XtPointer);
void   PlayVoiceCB       (Widget, XtPointer, XtPointer);
void   PlayAllCB         (Widget, XtPointer, XtPointer);
void   SetAppIcon        (Widget);
void   GetBell           (Display *);
void   SetBell           (Display *, int, int);
int    Pitch             (int);
void   PlayNote          (XtPointer, XtIntervalId *);
void   SoundCB           (Widget, XtPointer, XtPointer);
void   SetNoteCB         (Widget, XtPointer, XtPointer);
void   CreateScore       (Widget);
Widget CreateNotebook    (Widget);
void   CvtStrToFloat     (XrmValue *, Cardinal *, XrmValue *, XrmValue *);



/* Globals */
AppData      *appData;
XtAppContext  context;
int           orig_percent, orig_pitch, orig_duration;

Widget        key[1000];


String fallback[] = {
   "Piano*highlightThickness:     		0",
   "Piano*borderWidth:      			0",
   "Piano*iconImage:     			Piano.bmp",
   "Piano*borderWidth:     			0",
   "Piano*margin:     			0",
   "Piano*wKey.background:     		white",
   "Piano*bKey.background:     		black",
   "Piano*wKey.foreground:     		black",
   "Piano*bKey.foreground:     		white",
   "Piano*wKey.shadowThickness:     		2",
   "Piano*bKey.shadowThickness:     		4",
   "Piano*wKey.armColor:     			grey85",
   "Piano*bKey.armColor:     			grey0",
   "Piano*wKey.topShadowColor:     		white",
   "Piano*bKey.bottomShadowColor:     	black",
   "Piano*wKey.topShadowColor:     		grey60",
   "Piano*bKey.bottomShadowColor:     	grey20",
   "Piano*bKey.labelString:     		",
   "Piano*wKey.labelString:     		",
   
   "Piano*keyboard.marginWidth:		10",
   "Piano*keyboard.marginHeight:		10",
   
   "Piano*scoreWin.height:			111",
   "Piano*staff.width:			920",
   "Piano*staff.height:			100",
   
   "Piano*popupBtn1.labelString:		Add Voice",
   "Piano*popupBtn2.labelString:		Remove Voice",
   "Piano*popupBtn3.labelString:		Clear Voice",
   "Piano*popupBtn4.labelString:		Play Voice",
   "Piano*popupBtn5.labelString:		Play All",
   "Piano*popupBtn6.labelString:		Save Voice",
   "Piano*popupBtn7.labelString:		Load Voice",
   
   "Piano*cascade1.labelString:		File",
   "Piano*cascade2.labelString:		Help",

   "Piano*b1.labelString:			Quit",

   "Piano*notebook.orientation:		horizontal",
   "Piano*notebook.adjustLast:		false",
   "Piano*notebook*paneMaximum:		40",
   
   "Piano*dspPromptDlog.labelString:		Enter name of display to connect to:",
   "Piano*warnDlog.messageString:		Error in connecting to display",
   
   "Piano*helpDlog*messageString:\
Piano Demo\\n\
----------\\n\
Press Btn3 on a staff to post an associated menu\\n\
containing the following items:\\n\
   Add Voice - Add a new staff and voice. Each voice may\\n\
               connect to a different display.\\n\
   Remove Voice - Removes a staff and voice from the score.\\n\
   Clear Voice - Removes all notes in a staff.\\n\
   Play Voice - Plays the notes in the selected staff.\\n\
   Play All - Plays all voices in the score together.\\n\
   Save Voice - Saves the selected voice to a file.\\n\
   Load Voice - Loads a voice from a file. This will\\n\
                append to any existing notes in the voice.\\n\\n\
To delete a note, press Btn2 over the desired note in a staff.\\n\\n\
Settable resources are:\\n\
   baseDuration - sets the duration of a quarter note. (msec)\\n\
   baseFrequency - sets the frequency of bottom note. (Hz)\\n\
   useKeyboard - specifies if keyboard should play along.\\n\
   wkeyCount - number of white keys on the keyboard.\\n\
   keyHeight - initial height in pixels of the white keys.\\n\
   keyWidth - initial width in pixels of the white keys.",

   NULL
  };



/***********************************************************************/


/*----------------------------------------------------------------*
 |                         MyErrorHandler                         |
 *----------------------------------------------------------------*/
int MyErrorHandler (Display *display, XErrorEvent *errorEvent)
{
  /* this is most likely invoked when the frequency selected is out of range. */
  printf("X Error!\n");

  return 0;			/* Ignored by X. */
}


/*----------------------------------------------------------------*
 |                             DoQuit                             |
 *----------------------------------------------------------------*/
void DoQuit ()
{
  exit(0);
}


/*----------------------------------------------------------------*
 |                             DoHelp                             |
 *----------------------------------------------------------------*/
void DoHelp ()
{
  static Widget dlog = NULL;
  Arg           args[3];
  int           n;
  
  if (dlog == NULL)
    {
      dlog = XmCreateInformationDialog(appData->score, "helpDlog", NULL, 0);
      XtUnmanageChild( XmMessageBoxGetChild (dlog, XmDIALOG_HELP_BUTTON) );
      XtUnmanageChild( XmMessageBoxGetChild (dlog, XmDIALOG_CANCEL_BUTTON) );
    }
  
  XtManageChild(dlog);
}


/*----------------------------------------------------------------*
 |                             MenuCB                             |
 *----------------------------------------------------------------*/
void MenuCB (Widget w, XtPointer clientData, XtPointer callData)
{
  switch ((long)clientData)
    {
    case MENU_QUIT:   DoQuit(); break;
    case MENU_HELP:   DoHelp(); break;
    }
}


/*--------------------------------------------------------------------*
 |                            DoAddVoiceCB                            |
 *--------------------------------------------------------------------*/
void DoAddVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  XmSelectionBoxCallbackStruct *cb = (XmSelectionBoxCallbackStruct *)callData;
  String        dspName;
  Display      *newDisplay;
  String        appName, appClass;
  static Widget dlog = NULL;
  int           n, argc = 0;
  Arg           args[5];
  
  
  XtGetApplicationNameAndClass(XtDisplay(w), &appName, &appClass);
  XmStringGetLtoR(cb->value, XmSTRING_DEFAULT_CHARSET, &dspName);
  
  newDisplay = XtOpenDisplay(context, dspName, appName, appClass, NULL, 0, &argc, NULL);
  
  if (newDisplay != NULL)
    AddNewStaff(newDisplay, dspName);
  else
    {
      if (dlog == NULL)
	{
	  n = 0;
	  XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
	  XtSetArg(args[n], XmNdialogType,  XmDIALOG_ERROR);                  n++;
	  dlog = XmCreateWarningDialog(appData->score, "warnDlog", args, n);
	}
      XtManageChild(dlog);
    }
  if (dspName) XtFree(dspName);
}


/*--------------------------------------------------------------------*
 |                            AddVoiceCB                              |
 *--------------------------------------------------------------------*/
void AddVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  Cardinal      n;
  Arg           args[5];
  static Widget dlog = NULL;
  
  
  if (dlog == NULL)
    {
      n = 0;
      XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
      XtSetArg(args[n], XmNdialogType,  XmDIALOG_PROMPT);                 n++;
      dlog = XmCreatePromptDialog(appData->score, "dspPromptDlog", args, n);
      
      XtAddCallback(dlog, XmNokCallback, DoAddVoiceCB, NULL);
    }
  
  XtManageChild(dlog);
}


/*--------------------------------------------------------------------*
 |                           RemoveVoiceCB                            |
 *--------------------------------------------------------------------*/
void RemoveVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  Widget    staff = (Widget)clientData;
  StaffRec *staffData;
  
  staffData = GetStaffData(staff);

  if (staffData->divider != NULL)
    XtDestroyWidget(staffData->divider);
  XtDestroyWidget(staff);
  
  if (staffData->next != NULL)
    staffData->next->prev = staffData->prev;
  
  if (staffData->prev != NULL)
    staffData->prev->next = staffData->next;
  else
    appData->staffList = staffData->next;
  
  XtFree((char *)staffData);
}


/*--------------------------------------------------------------------*
 |                            ClearVoiceCB                            |
 *--------------------------------------------------------------------*/
void ClearVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  Widget   staff = (Widget)clientData;
  StaffRec *staffData;
  NoteRec  *notes, *np;
  
  
  staffData = GetStaffData(staff);
  
  if (staffData != NULL)
    {
      while (staffData->notes != NULL)
	{
	  np = staffData->notes;
	  staffData->notes = staffData->notes->next;
	  XtFree((char *)np);
	}
      
      XClearArea(XtDisplay(staff), XtWindow(staff), 0, 0, 0, 0, TRUE);
    }
}


/*--------------------------------------------------------------------*
 |                              ArmKey                                |
 *--------------------------------------------------------------------*/
void ArmKey (XtPointer clientData, XtIntervalId *id)
{
  Widget key = (Widget) clientData;
  XEvent event;
  XtCallbackList cbList;
  
  XtVaGetValues(key, XmNarmCallback, &cbList, NULL);
  XtVaSetValues(key, XmNarmCallback, NULL, NULL);
  XtCallActionProc(key, "Arm", &event, NULL, 0);
  XtVaSetValues(key, XmNarmCallback, cbList, NULL);
}


/*--------------------------------------------------------------------*
 |                            DisarmKey                               |
 *--------------------------------------------------------------------*/
void DisarmKey (XtPointer clientData, XtIntervalId *id)
{
  Widget key = (Widget) clientData;
  XEvent event;
   
  XtCallActionProc(key, "Disarm", &event, NULL, 0);
}


/*--------------------------------------------------------------------*
 |                             PlayNotes                              |
 | Note that bell duration and interval timing are defined in X as    |
 | based on milliseconds.  Add a timeout for each note to play, then  |
 | let things go.                                                     |
 *--------------------------------------------------------------------*/
void PlayNotes (XtPointer clientData, XtIntervalId *id)
{
  NoteRec *note = (NoteRec *)clientData;
  XEvent event;
  XtCallbackList tempCallbackList;
  int dt = 0;
  

  while (note != NULL)
    {
      if (note->noteType < REST)
	{
	  XtAppAddTimeOut(context, dt, PlayNote, note);

	  /* now to press the keys. */
	  if (appData->useKeyboard)
	    {
	      XtAppAddTimeOut(context, dt, ArmKey, key[note->noteNumber]);
	      XtAppAddTimeOut(context, dt+note->noteDuration, DisarmKey,
			      key[note->noteNumber]);
	    }
	}
      dt += note->noteDuration;

      note = note->next;
    }
}

/*--------------------------------------------------------------------*
 |                            PlayVoiceCB                             |
 *--------------------------------------------------------------------*/
void PlayVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  Widget    staff = (Widget)clientData;
  StaffRec *staffData;
  NoteRec  *notes;
  
  
  staffData = GetStaffData(staff);
  if (staffData != NULL)
    XtAppAddTimeOut(context, 1, PlayNotes, staffData->notes);
}


/*--------------------------------------------------------------------*
 |                             PlayAllCB                              |
 *--------------------------------------------------------------------*/
void PlayAllCB (Widget staff, XtPointer clientData, XtPointer callData)
{
  StaffRec *sPtr;
  
  for (sPtr = appData->staffList; sPtr != NULL; sPtr = sPtr->next)
    {
      XtAppAddTimeOut(context, 1, PlayNotes, sPtr->notes);
    }
}


/*--------------------------------------------------------------------*
 |                            DoSaveVoiceCB                           |
 *--------------------------------------------------------------------*/
void DoSaveVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  XmFileSelectionBoxCallbackStruct *fdata =
    (XmFileSelectionBoxCallbackStruct *)callData;
  Widget    staff = (Widget)clientData;
  StaffRec *staffData;
  NoteRec  *note;
  FILE     *fp;
  char     *fileName;
  static Widget errDlog = NULL;
  
  
  if (fdata->length > 0)
    {
      XmStringGetLtoR(fdata->value, XmSTRING_DEFAULT_CHARSET, &fileName);
      
      staffData = GetStaffData(staff);
      if (staffData != NULL)
	{
	  fp = fopen(fileName, "w");
	  
	  for (note = staffData->notes;  note != NULL;  note = note->next)
	    {
	      fprintf(fp, "%d %d %d %d %d\n",
		      note->noteType,
		      note->noteDuration,
		      note->noteNumber,
		      note->noteIndex,
		      note->ledgerLine);
	    }
	  fclose(fp);
	}
      
      if (fileName) XtFree(fileName);
    }
}



/*--------------------------------------------------------------------*
 |                            SaveVoiceCB                             |
 *--------------------------------------------------------------------*/
void SaveVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  Widget        staff = (Widget)clientData;
  static Widget fsdlog = NULL;
  static Widget oldStaff;
  
  
  if (fsdlog == NULL)
    {
      fsdlog = XmCreateFileSelectionDialog(staff, "saveDlog", NULL, 0);
      XtVaSetValues(fsdlog,
		    XmNautoUnmanage, True,
		    XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL,
		    NULL);
    }
  else
    {
      XtRemoveCallback(fsdlog, XmNokCallback, DoSaveVoiceCB, oldStaff);
    }
  
  XtAddCallback(fsdlog, XmNokCallback, DoSaveVoiceCB, staff);
  oldStaff = staff;
  
  XtManageChild(fsdlog);
}



/*--------------------------------------------------------------------*
 |                           DoLoadVoiceCB                            |
 *--------------------------------------------------------------------*/
void DoLoadVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  XmFileSelectionBoxCallbackStruct *fdata =
    (XmFileSelectionBoxCallbackStruct *)callData;
  Widget    staff = (Widget)clientData;
  StaffRec *staffData;
  NoteRec  *note, *tail;
  FILE     *fp;
  Boolean   done = FALSE;
  int       noteOffset;
  char     *fileName;
  
  
  if (fdata->length > 0)
    {
      XmStringGetLtoR(fdata->value, XmSTRING_DEFAULT_CHARSET, &fileName);
      
      fp = fopen(fileName, "r");
      if (fileName) XtFree(fileName);
      
      if (fp != NULL)
	{
	  staffData = GetStaffData(staff);
	  if (staffData->notes == NULL)
	    {
	      tail = NULL;
	      noteOffset = 0;
	    }
	  else
	    for (tail=staffData->notes, noteOffset = 1;
		 tail->next != NULL;
		 tail = tail->next, noteOffset++);
	  
	  while (!done)
	    {
	      note = (NoteRec *) XtMalloc(sizeof(NoteRec));
	      if (fscanf(fp, "%d %d %d %d %d\n",
			 &note->noteType,
			 &note->noteDuration,
			 &note->noteNumber,
			 &note->noteIndex,
			 &note->ledgerLine)
		  > 0)
		{
		  note->noteIndex += noteOffset;
		  note->display = staffData->display;
		  note->next = NULL;
		  
		  if (tail == NULL)
		    {
		      staffData->notes = note;
		      tail = note;
		    }
		  else
		    {
		      tail->next = note;
		      tail = tail->next;
		    }
		}
	      else
		{
		  XtFree((XtPointer)note);
		  done = TRUE;
		}
	    }
	  fclose(fp);
	}
      
      XClearArea(XtDisplay(w), XtWindow(staff), 0, 0, 0, 0, TRUE);
    }
}




/*--------------------------------------------------------------------*
 |                            LoadVoiceCB                             |
 *--------------------------------------------------------------------*/
void LoadVoiceCB (Widget w, XtPointer clientData, XtPointer callData)
{
  Widget        staff = (Widget)clientData;
  static Widget fsdlog = NULL;
  static Widget oldStaff;
  
  
  if (fsdlog == NULL)
    {
      fsdlog = XmCreateFileSelectionDialog(staff, "loadDlog", NULL, 0);
      XtVaSetValues(fsdlog,
		    XmNautoUnmanage, True,
		    XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL,
		    NULL);
    }
  else
    {
      XtRemoveCallback(fsdlog, XmNokCallback, DoLoadVoiceCB, oldStaff);
    }
  
  XtAddCallback(fsdlog, XmNokCallback, DoLoadVoiceCB, staff);
  oldStaff = staff;
  
  XtManageChild(fsdlog);
}


/*--------------------------------------------------------------------*
 |                              DrawNotes                             |
 | x1,x2 specify the clipping width. If they are the same value, no   |
 | clipping is performed.                                             |
 *--------------------------------------------------------------------*/
void DrawNotes (Widget staff, int x1, int x2)
{
  StaffRec *staffData;
  NoteRec  *notes, *np;
  
  staffData = GetStaffData(staff);
  if (staffData != NULL)
    for (np = staffData->notes;  np != NULL;  np = np->next)
      DrawNote(staff, np, x1, x2);
}



/*--------------------------------------------------------------------*
 |                             DrawStaffCB                            |
 *--------------------------------------------------------------------*/
void DrawStaffCB (Widget staff, XtPointer clientData, XtPointer callData)
{
  XExposeEvent *expEvt =
    (XExposeEvent *)((XmDrawingAreaCallbackStruct*)callData)->event;
  int       i, y;
  Dimension width, height;


  if (expEvt->count > 1)
    return;

  XtVaGetValues(XtParent(staff), XmNwidth, &width, NULL);
  XtVaGetValues(staff, XmNwidth, &width, XmNheight, &height, NULL);
  
  for (i=4; i<=12; i+=2)
    {
      y = i*(int)height / 16;
      XDrawLine(XtDisplay(staff), XtWindow(staff),
		DefaultGCOfScreen(XtScreen(staff)),
		0, y, width, y);
    }
  
  DrawNotes(staff, expEvt->x, expEvt->x + expEvt->width);
}




/*-------------------------------------------------------------*
 |                          SetAppIcon()                       |
 *-------------------------------------------------------------*/
void SetAppIcon(Widget shell)
{
  Pixmap  iconPixmap;
  
  iconPixmap = XCreateBitmapFromData(XtDisplay(shell), XtScreen(shell)->root,
				     (char*)piano_bits, piano_width, piano_height);
  
  XtVaSetValues(shell, XmNiconPixmap, iconPixmap, NULL);
}



/*--------------------------------------------------------------------*
 |                            GetBell                                 |
 *--------------------------------------------------------------------*/
void GetBell(Display *dpy)
{
  XKeyboardState stateValues;
  
  XGetKeyboardControl(dpy, &stateValues);
  
  orig_percent  = stateValues.bell_percent;
  orig_pitch    = stateValues.bell_pitch;
  orig_duration = stateValues.bell_duration;
}



/*--------------------------------------------------------------------*
 |                             SetBell                                |
 *--------------------------------------------------------------------*/
void SetBell(Display *dpy, int pitch, int duration)
{
  XKeyboardControl controlValues;
  unsigned long    valueMask = KBBellPercent | KBBellPitch | KBBellDuration;
  
  controlValues.bell_percent  = orig_percent;
  controlValues.bell_pitch    = pitch;
  controlValues.bell_duration = duration;
  
  XChangeKeyboardControl(dpy, valueMask, &controlValues);
}



/*--------------------------------------------------------------------*
 |                            Pitch                                   |
 *--------------------------------------------------------------------*/
int Pitch (int note)
{
  double x, m, n, f;
  
  /* notes are calculated from the base frequency. */
  /* This is the first note on the keyboard.       */
  /* The frequency of a note = 2^(index / 12).     */
  
  x = (double)2.0;
  m = (double)note;
  n = (double)12.0;
  
  f = (double)appData->baseFrequency * pow(x, (m/n));
  
  return((int)f);
}


/*--------------------------------------------------------------------*
 |                             PlayNote                               |
 *--------------------------------------------------------------------*/
void PlayNote (XtPointer clientData, XtIntervalId *id)
{
  NoteRec *note = (NoteRec *)clientData;

  SetBell(note->display, Pitch(note->noteNumber), note->noteDuration);
   
  XBell(note->display, 100);
   
  SetBell(note->display, orig_pitch, orig_duration);
}


/*--------------------------------------------------------------------*
 |                             SoundCB                                |
 *--------------------------------------------------------------------*/
void SoundCB (Widget w, XtPointer noteNumber, XtPointer callData)
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)callData;
  NoteRec note;

  /* only play the note if this is a true arm event. */

  if (cb->event != NULL)
    {
      note.display      = XtDisplay(w);
      note.noteType     = EIGHTH;
      note.noteNumber   = (NoteType)noteNumber;
      note.noteIndex    = 0;
      note.ledgerLine   = 0;
      note.noteDuration = appData->baseDuration;

      PlayNote(&note, 0);
    }
}


#define MUG_SHOTS 11
/*--------------------------------------------------------------------*
 |                             BuildKeys                              |
 *--------------------------------------------------------------------*/
void BuildKeys (Widget parent)
{
  Pixmap   iconPixmaps[MUG_SHOTS+1];
  int      i, j = 0, imageCount=MUG_SHOTS;
  Boolean  pixmapsSet = FALSE, easterEgg = False;
  int      noteCount = appData->wkeyCount;
  static Boolean firstTime = True;
  
  
  if (firstTime)
    {
      int          x, y, junk;
      unsigned int bjunk;
      Window       wjunk;

      firstTime = False;
	  
      /* dev team's signature... :-) */
      XQueryPointer(XtDisplay(parent), RootWindowOfScreen(XtScreen(parent)),
		    &wjunk, &wjunk, &x, &y, &junk, &junk, &bjunk);

      easterEgg = (x+y == 0);

      if (easterEgg)
	{
	  Display       *dsp = XtDisplay(parent);
	  Window         win = RootWindowOfScreen(XtScreen(parent));
	  Pixmap         shapemask;
	  XpmAttributes  attributes;
	    
	  attributes.valuemask = 0;
	  XmeXpmCreatePixmapFromData(dsp, win, none,     &(iconPixmaps[0]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, andrew,   &(iconPixmaps[1]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, dan,      &(iconPixmaps[2]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, dave,     &(iconPixmaps[3]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, doug,     &(iconPixmaps[4]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, ellis,    &(iconPixmaps[5]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, ingeborg, &(iconPixmaps[6]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, jim,      &(iconPixmaps[7]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, kamesh,   &(iconPixmaps[8]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, scott,    &(iconPixmaps[9]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, steve,    &(iconPixmaps[10]), &shapemask, &attributes);
	  XmeXpmCreatePixmapFromData(dsp, win, vania,    &(iconPixmaps[11]), &shapemask, &attributes);

	  appData->wkeyCount = noteCount = imageCount;
	}
    }


  XtVaSetValues(parent, XmNfractionBase, noteCount * 10, NULL);
  
  j = 0;
  for (i=0; i<noteCount; i++)
    if ( !((i+1)%7) || !((i-2)%7) ) j++; /* handle 2 whitekeys in a row. ie: B-C, E-F. */
    else
      {
	j = j+2;
	key[j] = XtVaCreateManagedWidget("bKey", xmPushButtonWidgetClass, parent,
					 XmNwidth,            appData->keyWidth,
					 XmNleftAttachment,   XmATTACH_POSITION,
					 XmNleftPosition,     i*10 + 7,
					 XmNrightAttachment,  XmATTACH_POSITION,
					 XmNrightPosition,    i*10 + 13,
					 XmNtopAttachment,    XmATTACH_FORM,
					 XmNbottomAttachment, XmATTACH_POSITION,
					 XmNbottomPosition,   noteCount*6,
					 NULL);
	XtAddCallback(key[j], XmNarmCallback, SoundCB, (XtPointer)(long)j);
      }
  
  j = 0;
  for (i=0; i<noteCount; i++)
    {
      if ( !(i%7) || !((i-3)%7) ) j--;
      j = j+2;
      key[j] = XtVaCreateManagedWidget("wKey", xmPushButtonWidgetClass, parent,
				       XmNheight,           appData->keyHeight,
				       XmNleftAttachment,   XmATTACH_POSITION,
				       XmNleftPosition,     i*10,
				       XmNrightAttachment,  XmATTACH_POSITION,
				       XmNrightPosition,    (i+1)*10,
				       XmNtopAttachment,    XmATTACH_FORM,
				       XmNbottomAttachment, XmATTACH_FORM,
				       NULL);
      XtAddCallback(key[j], XmNarmCallback, SoundCB, (XtPointer)(long)j);
      
      if (easterEgg)
	{
	  XtVaSetValues(key[j],
			XmNlabelType,   XmPIXMAP,
			XmNlabelPixmap, iconPixmaps[0],
			XmNarmPixmap,   iconPixmaps[(i%imageCount)+1],
			XmNmarginLeft,  0,
			XmNmarginRight, 0,
			XmNmarginTop,   100,
			NULL);
	}
    }
}


/*--------------------------------------------------------------------*
 |                           CreateKeyboard                           |
 *--------------------------------------------------------------------*/
Widget CreateKeyboard(Widget parent)
{
   int    i, j = 0;
   Widget keyBoard, wKey, bKey;
   
   
   keyBoard = XtVaCreateWidget("keyBoard", xmFormWidgetClass, parent, NULL);
   BuildKeys(keyBoard);
   
   XtManageChild(keyBoard);
   return(keyBoard);
}



/*--------------------------------------------------------------------*
 |                            SetIcon                                 |
 *--------------------------------------------------------------------*/
void SetIcon (Widget w, Pixmap cursorPixmap)
{
   Pixel     fgPix, bgPix;
   Cursor    cursor;
   XColor    xcolors[2];
   Display  *dsp = XtDisplay(w);
   
   
   xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(dsp));
   xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(dsp));
   
   XQueryColors(dsp, DefaultColormapOfScreen(DefaultScreenOfDisplay(dsp)), xcolors, 2);
   
   cursor = XCreatePixmapCursor(dsp, cursorPixmap, cursorPixmap,
				&(xcolors[0]), &(xcolors[1]), note_x_hot, note_y_hot);
   
   XDefineCursor(dsp, XtWindow(w), cursor);
}


/*--------------------------------------------------------------------*
 |                           GetStaffData                             |
 | This scans the list of staffs for a match.  It returns the data    |
 | associated with the staff.                                         |
 *--------------------------------------------------------------------*/
StaffRec *GetStaffData (Widget staff)
{
  StaffRec *sPtr;

  for (sPtr = appData->staffList; sPtr != NULL; sPtr = sPtr->next)
    {
      if (sPtr->staff == staff)
	return (sPtr);
    }

  /* should never get here */
  return (NULL);
}


/*--------------------------------------------------------------------*
 |                             DrawNote                               |
 *--------------------------------------------------------------------*/
void DrawNote (Widget staff, NoteRec *note, int x1, int x2)
{
   Dimension  width, height;
   Pixmap     notePix, notePixMask;
   int        x, y;
   

   notePix = appData->noteTable[note->noteType].image;
   notePixMask = appData->noteTable[note->noteType].mask;

   XtVaGetValues(staff, XmNwidth, &width, XmNheight, &height, NULL);
   
   x = note->noteIndex * 15;
   y = (15 - note->ledgerLine) * (int)height / 16 - (note_height/2) - 4;

   /* if the position is off the right side of the staff, resize it. */
   if ((x + note_width) > (int)width)
     XtVaSetValues(staff, XmNwidth, x + 2*note_width, NULL);
   
   if ((x1 != x2) && (x < x1-note_width || x > x2+note_width))
     return;

   XSetClipMask  (XtDisplay(staff), appData->noteGC, notePixMask);
   XSetClipOrigin(XtDisplay(staff), appData->noteGC, x, y);
   XCopyArea(XtDisplay(staff), notePix, XtWindow(staff), appData->noteGC,
	     0, 0, note_width, note_height,
	     x, y);
}


/*--------------------------------------------------------------------*
 |                            SetActiveNote                           |
 *--------------------------------------------------------------------*/
void SetActiveNote (Widget w, NoteType noteType)
{
   appData->activeNoteType = noteType;

   SetIcon(appData->score, appData->noteTable[noteType].mask);

   XSetClipMask(XtDisplay(w), appData->noteGC, appData->noteTable[noteType].mask);
}



/*--------------------------------------------------------------------*
 |                              SetNoteCB                             |
 | callback which sets the active note and modifies the cursor.       |
 *--------------------------------------------------------------------*/
void SetNoteCB (Widget w, XtPointer clientData, XtPointer callData)
{
   NoteType noteType = (NoteType)clientData;

   SetActiveNote(w, noteType);
}



/*--------------------------------------------------------------------*
 |                             NoteNumber                             |
 *--------------------------------------------------------------------*/
int NoteNumber (int ledgerLine, Boolean isASharp)
{
   int n = 0;
   
   switch (ledgerLine)
     {
     case 1:  n = 1;  break;
     case 2:  n = 3;  break;
     case 3:  n = 5;  break;
     case 4:  n = 6;  break;
     case 5:  n = 8;  break;
     case 6:  n = 10; break;
     case 7:  n = 12; break;
     case 8:  n = 13; break;
     case 9:  n = 15; break;
     case 10: n = 17; break;
     case 11: n = 18; break;
     case 12: n = 20; break;
     }
   
   if (isASharp)
     return (n+1);
   else
     return (n);
}


/*--------------------------------------------------------------------*
 |                         DeleteNoteAtPosn                           |
 *--------------------------------------------------------------------*/
void DeleteNoteAtPosn (Widget staff, int x, int y)
{
   int        ledgerLine, noteIndex, i;
   Dimension  height;
   StaffRec  *staffData;
   NoteRec   *np, *npTemp;
   
   
   /* find the corresponding ledger line in the staff. */
   XtVaGetValues(staff, XmNheight, &height, NULL);
   ledgerLine = 15 - (16 * y / (int)height);
   
   noteIndex = x / 15;
   
   staffData = GetStaffData(staff);
   if ((staffData != NULL) && (staffData->notes != NULL))
     {
	if (noteIndex == 1)
	  {
	     npTemp = staffData->notes;
	     staffData->notes = staffData->notes->next;
	  }
	else
	  {
	     for (np = staffData->notes;
		  ((noteIndex > 2) && (np->next != NULL));
		  noteIndex--)
	       np = np->next;

	     if (np->next != NULL)
	       {
		 npTemp = np->next;
		 np->next = np->next->next;
	       }
	     else
	       npTemp = NULL;
	  }
	if (npTemp != NULL) XtFree((XtPointer)npTemp);

	for (np = staffData->notes, i = 0; np != NULL; np = np->next)
	  np->noteIndex = ++i;
	XClearArea(XtDisplay(staff), XtWindow(staff), 0, 0, 0, 0, TRUE);
     }

}



/*--------------------------------------------------------------------*
 |                           AddNoteAtPosn                            |
 *--------------------------------------------------------------------*/
void AddNoteAtPosn (Widget staff, int y, NoteType noteType)
{
   int        ledgerLine, noteCount, noteDuration;
   Dimension  height;
   StaffRec  *staffData;
   NoteRec   *noteList, *currentNoteList, *np;
   Boolean    isASharp = FALSE;
   
   
   /* find the corresponding ledger line in the staff. */
   XtVaGetValues(staff, XmNheight, &height, NULL);
   ledgerLine = 15 - (16 * y / (int)height);
   
   /* round up to G and down to middle C. */
   if (ledgerLine <  1) ledgerLine = 1;
   else
     if (ledgerLine > 12) ledgerLine = 12;
   
   switch (noteType)
     {
     case EIGHTH:	   noteDuration = appData->baseDuration;				break;
     case EIGHTHDOT:	   noteDuration = appData->baseDuration*3/2;				break;
     case EIGHTHSHARP:	   noteDuration = appData->baseDuration;	isASharp = TRUE;	break;
     case EIGHTHDOTSHARP:  noteDuration = appData->baseDuration*3/2;	isASharp = TRUE;	break;
     case QUARTER:	   noteDuration = appData->baseDuration*2;				break;
     case QUARTERDOT:	   noteDuration = appData->baseDuration*3;				break;
     case QUARTERSHARP:	   noteDuration = appData->baseDuration*2;	isASharp = TRUE;	break;
     case QUARTERDOTSHARP: noteDuration = appData->baseDuration*3;	isASharp = TRUE;	break;
     case HALF:		   noteDuration = appData->baseDuration*4;				break;
     case HALFDOT:	   noteDuration = appData->baseDuration*6;				break;
     case HALFSHARP:	   noteDuration = appData->baseDuration*4;	isASharp = TRUE;	break;
     case HALFDOTSHARP:	   noteDuration = appData->baseDuration*6;	isASharp = TRUE;	break;
     case REST:		   noteDuration = appData->baseDuration;				break;
     case RESTDOT:	   noteDuration = appData->baseDuration*3/2;				break;
     default:              noteDuration = 0;
     }
   
   /* get the staff info - this tells the display to use. */
   staffData = GetStaffData(staff);
   
   noteList = (NoteRec *)XtMalloc(sizeof(NoteRec));
   noteList->display       = staffData->display;
   noteList->noteType      = noteType;
   noteList->noteDuration  = noteDuration;
   noteList->noteNumber    = NoteNumber(ledgerLine, isASharp);
   noteList->ledgerLine    = ledgerLine;
   noteList->next          = NULL;
   
   /* get the current list of notes. */
   currentNoteList = staffData->notes;
   
   /* find out how many there are. */
   for (noteCount=1, np=currentNoteList;
	((np != NULL) && (np->next != NULL));
	np=np->next, noteCount++)
     ;
   
   if (np == NULL)
     {
	staffData->notes = noteList;
	noteList->noteIndex = noteCount;
     }
   else
     {
	np->next = noteList;
	noteList->noteIndex = noteCount+1;
     }
   
   DrawNote(staff, noteList, 0, 0);
}


/*--------------------------------------------------------------------*
 |                          AddNoteToStaffCB                          |
 *--------------------------------------------------------------------*/
void AddNoteToStaffCB (Widget staff, XtPointer clientData, XtPointer callData)
{
   XmDrawingAreaCallbackStruct *cb = (XmDrawingAreaCallbackStruct *)callData;
   XButtonEvent *btnEvent = (XButtonEvent *)cb->event;
   int           vposn, hposn;
   
   
   if ((btnEvent->button == Button1) && (btnEvent->type == ButtonPress))
     {
	AddNoteAtPosn(staff, btnEvent->y, appData->activeNoteType);
     }
   else
     
     if ((btnEvent->button == Button2) && (btnEvent->type == ButtonPress))
       {
	  DeleteNoteAtPosn(staff, btnEvent->x, btnEvent->y);
       }
}


/*--------------------------------------------------------------------*
 |                             AddNewStaff                            |
 | Creates data for a new staff and adds it to the global score.      |
 | A popup menu is attached to the staff.                             |
 *--------------------------------------------------------------------*/
void AddNewStaff (Display *newDisplay, char *dspName)
{
   StaffRec *staffData;
   
   
   staffData = (StaffRec *) XtMalloc(sizeof(StaffRec));
   staffData->display       = newDisplay;
   staffData->notes         = NULL;
   staffData->next = staffData->prev = NULL;
   
   /* if this is not the first staff, the add a seperator. */
   if (appData->staffList != NULL)
     staffData->divider = 
       XtVaCreateManagedWidget("divider", xmSeparatorWidgetClass, appData->score, NULL);
   else
     staffData->divider = NULL;

   staffData->staff =
     XtVaCreateManagedWidget("staff", xmDrawingAreaWidgetClass, appData->score,
			     XmNresizePolicy, XmRESIZE_NONE,
			     NULL);
   XtAddCallback(staffData->staff, XmNexposeCallback, DrawStaffCB,      staffData);
   XtAddCallback(staffData->staff, XmNinputCallback,  AddNoteToStaffCB, staffData);
   
   CreateStaffMenu(appData->score, staffData->staff, dspName);
   
   /*
    * add the staff to the staff list.
    */
   staffData->next = appData->staffList;
   if (appData->staffList != NULL)
     appData->staffList->prev = staffData;
   appData->staffList = staffData;
}



/*--------------------------------------------------------------------*
 |                            PostStaffMenu  		              |
 *--------------------------------------------------------------------*/
void PostStaffMenu (Widget w, XtPointer clientData, XEvent *event, Boolean *dispatch)
{
   Widget        menu     = (Widget)clientData;
   XButtonEvent *btnEvent = (XButtonEvent *)event;
   int           button;
   
   
   XtVaGetValues(menu, XmNwhichButton, &button, NULL);
   if (btnEvent->button == button)
     {
	XmMenuPosition(menu, btnEvent);
	XtManageChild(menu);
     }
}


/*--------------------------------------------------------------------*
 |                           CreateStaffMenu                          |
 *--------------------------------------------------------------------*/
void CreateStaffMenu (Widget score, Widget staff, char *dspName)
{
   Widget popupMenu, popupBtn[8];
   
   
   popupMenu = XmCreatePopupMenu(staff, "popupMenu", NULL, 0);
   XtAddEventHandler(staff, ButtonPressMask, False, PostStaffMenu, popupMenu);
   
   XtVaCreateManagedWidget(dspName, xmLabelWidgetClass,     popupMenu, NULL);
   XtVaCreateManagedWidget("line",  xmSeparatorWidgetClass, popupMenu, NULL);
   popupBtn[1] = XtVaCreateManagedWidget("popupBtn1", xmPushButtonWidgetClass, popupMenu, NULL);
   popupBtn[2] = XtVaCreateManagedWidget("popupBtn2", xmPushButtonWidgetClass, popupMenu, NULL);
   popupBtn[3] = XtVaCreateManagedWidget("popupBtn3", xmPushButtonWidgetClass, popupMenu, NULL);
   popupBtn[4] = XtVaCreateManagedWidget("popupBtn4", xmPushButtonWidgetClass, popupMenu, NULL);
   popupBtn[5] = XtVaCreateManagedWidget("popupBtn5", xmPushButtonWidgetClass, popupMenu, NULL);
   popupBtn[6] = XtVaCreateManagedWidget("popupBtn6", xmPushButtonWidgetClass, popupMenu, NULL);
   popupBtn[7] = XtVaCreateManagedWidget("popupBtn7", xmPushButtonWidgetClass, popupMenu, NULL);

   /* if this is the first one, then don't allow it to be removed. */
   if (appData->staffList == NULL) XtVaSetValues(popupBtn[2], XmNsensitive, False, NULL);
   
   XtAddCallback(popupBtn[1], XmNactivateCallback, AddVoiceCB,    NULL);
   XtAddCallback(popupBtn[2], XmNactivateCallback, RemoveVoiceCB, staff);
   XtAddCallback(popupBtn[3], XmNactivateCallback, ClearVoiceCB,  staff);
   XtAddCallback(popupBtn[4], XmNactivateCallback, PlayVoiceCB,   staff);
   XtAddCallback(popupBtn[5], XmNactivateCallback, PlayAllCB,     NULL);
   XtAddCallback(popupBtn[6], XmNactivateCallback, SaveVoiceCB,   staff);
   XtAddCallback(popupBtn[7], XmNactivateCallback, LoadVoiceCB,   staff);
   
}



/*--------------------------------------------------------------------*
 |                             CreateScore                            |
 | Creates the rowcolumn to holds the staffs.  Also creates an option |
 | menu for manipulating the staffs. The staff is inserted into the   |
 | global appData list of scores.                                     |
 *--------------------------------------------------------------------*/
void CreateScore(Widget parent)
{
   Widget scoreWin;
   
   scoreWin = XtVaCreateManagedWidget("scoreWin", xmScrolledWindowWidgetClass, parent,
				       XmNscrollingPolicy, XmAUTOMATIC, NULL);
   appData->score =
     XtVaCreateManagedWidget("score", xmRowColumnWidgetClass, scoreWin,
			     XmNadjustLast,   FALSE,
			     XmNnumColumns,   1,
			     XmNorientation,  XmVERTICAL,
			     XmNpacking,      XmPACK_TIGHT,
			     NULL);
   AddNewStaff(XtDisplay(appData->score), LOCAL_NAME);
}


/*--------------------------------------------------------------------*
 |                           CreateNotebook                           |
 *--------------------------------------------------------------------*/
Widget CreateNotebook(Widget parent)
{
   NoteType noteType;
   Widget   notebook, noteButton[LAST_NOTE];
   Pixel    fg, bg;
   Display *dsp = XtDisplay(parent);
   Window   win = RootWindowOfScreen(XtScreen(parent));
   int      d   = DefaultDepthOfScreen(XtScreen(parent));
   
   
   notebook  = XtVaCreateManagedWidget("notebook", xmRowColumnWidgetClass, parent, NULL);

   /*
    * create a pushbutton for each note type and setup its callback.
    */
   for (noteType = (NoteType)0;  noteType < LAST_NOTE;  noteType++)
     {
	noteButton[noteType] =
	  XtVaCreateManagedWidget(noteName[noteType],
				  xmPushButtonWidgetClass, notebook,
				  XmNlabelType,   XmPIXMAP,
				  XmNlabelPixmap, appData->noteTable[noteType].image,
				  NULL);
	XtAddCallback(noteButton[noteType], XmNactivateCallback, SetNoteCB,
		      (XtPointer)noteType);
     }

   return notebook;
}


/*-------------------------------------------------------------*
 |	   Resource Converter:    CvtStrToFloat                |
 *-------------------------------------------------------------*/
void CvtStrToFloat (XrmValue *args, Cardinal *nargs, XrmValue *fromVal, XrmValue *toVal)
{
  static float result;

  if (sscanf((char *)fromVal->addr, "%f", &result) == 1)
    {
      toVal->size = sizeof(float);
      toVal->addr = (XtPointer) &result;
    }
  else
    XtStringConversionWarning((char *)fromVal->addr, "Float");
}
  
/*----------------------------------------------------------------*
 |			  GetAppResources	 	          |
 | The following resources are supported in piano:                |
 | .baseDuration:  (int)                                          |
 |     -- frequencey in Hz for middle C.                          |
 | .baseFrequency: (float)                                        |
 |     -- duration in ms of a quarter note.                       |
 | .wkeyCount:     (int)                                          |
 |     -- specifies the number of white keys.                     |
 | .keyHeight:     (int)                                          |
 |     -- white key pixel height. black keys are calculated.      |
 | .keyWidth:      (int)                                          |
 |     -- white key pixel width. black keys are calculated.       |
 *----------------------------------------------------------------*/
AppData *GetAppResources (Widget w)
{
   AppData *appData;

   if ((appData = (AppData *) XtCalloc(1, sizeof(AppData))) == NULL)
     {
	printf(EMSG1);
	exit(0);
     }

   XtGetApplicationResources(w, (XtPointer)appData,
			     appRes, XtNumber(appRes), NULL, 0);

   return (appData);
}


/*--------------------------------------------------------------------*
 |                         GetNoteImagePixmap                         |
 *--------------------------------------------------------------------*/
Pixmap GetNoteImagePixmap(Widget w, NoteType note)
{
   Pixel    fg, bg;
   Display *dsp = XtDisplay(w);
   Window   win = RootWindowOfScreen(XtScreen(w));
   int      d   = DefaultDepthOfScreen(XtScreen(w));
   unsigned char    *data = NULL;


   XtVaGetValues(w, XmNforeground, &fg, XmNbackground, &bg, NULL);

   switch (note)
     {
     case EIGHTH:          data = eighth_bits;		 break;
     case EIGHTHDOT:       data = eighth_dot_bits;	 break;
     case EIGHTHSHARP:     data = eighth_sharp_bits;	 break;
     case EIGHTHDOTSHARP:  data = eighth_dot_sharp_bits; break;
     case QUARTER:         data = quarter_bits;		 break;
     case QUARTERDOT:      data = quarter_dot_bits;	 break;
     case QUARTERSHARP:    data = quarter_sharp_bits;	 break;
     case QUARTERDOTSHARP: data = quarter_dot_sharp_bits;break;
     case HALF:            data = half_bits;		 break;
     case HALFDOT:         data = half_dot_bits;	 break;
     case HALFSHARP:       data = half_sharp_bits;	 break;
     case HALFDOTSHARP:    data = half_dot_sharp_bits;	 break;
     case REST:            data = rest_bits;		 break;
     case RESTDOT:         data = rest_dot_bits;	 break;
     default: ;
     }

   return(XCreatePixmapFromBitmapData(dsp, win, (char*)data,
				      note_width, note_height, fg, bg, d));
}



/*--------------------------------------------------------------------*
 |                         GetNoteMaskPixmap                          |
 *--------------------------------------------------------------------*/
Pixmap GetNoteMaskPixmap(Widget w, NoteType note)
{
   Pixel    fg, bg;
   Display *dsp = XtDisplay(w);
   Window   win = RootWindowOfScreen(XtScreen(w));
   int      d   = DefaultDepthOfScreen(XtScreen(w));
   unsigned char    *data = NULL;


   XtVaGetValues(w, XmNforeground, &fg, XmNbackground, &bg, NULL);

   switch (note)
     {
     case EIGHTH:          data = eighth_bits;		 break;
     case EIGHTHDOT:       data = eighth_dot_bits;	 break;
     case EIGHTHSHARP:     data = eighth_sharp_bits;	 break;
     case EIGHTHDOTSHARP:  data = eighth_dot_sharp_bits; break;
     case QUARTER:         data = quarter_bits;		 break;
     case QUARTERDOT:      data = quarter_dot_bits;	 break;
     case QUARTERSHARP:    data = quarter_sharp_bits;	 break;
     case QUARTERDOTSHARP: data = quarter_dot_sharp_bits;break;
     case HALF:            data = half_bits;		 break;
     case HALFDOT:         data = half_dot_bits;	 break;
     case HALFSHARP:       data = half_sharp_bits;	 break;
     case HALFDOTSHARP:    data = half_dot_sharp_bits;	 break;
     case REST:            data = rest_bits;		 break;
     case RESTDOT:         data = rest_dot_bits;	 break;
     default: ;
     }

   return (XCreatePixmapFromBitmapData(dsp, win, (char*)data,
				       note_width, note_height, 1, 0, 1));
}

/*--------------------------------------------------------------------*
 |                           BuildNoteTable                           |
 *--------------------------------------------------------------------*/
void BuildNoteTable (Widget w)
{
   NoteType i;

   for (i = (NoteType)0; i<LAST_NOTE; i++)
     {
	appData->noteTable[i].image     = GetNoteImagePixmap(w, i);
	appData->noteTable[i].mask      = GetNoteMaskPixmap(w, i);
     }
}


/*--------------------------------------------------------------------*
 |                          CreateMenuBar                             |
 *--------------------------------------------------------------------*/
void CreateMenuBar (Widget parent)
{
  Cardinal n;
  Arg      args[10];
  Widget   menuBar;
  Widget   cascade1, cascade2;
  Widget   menuPane1, menuPane2;
  Widget   b1;

  menuBar   = XmCreateMenuBar(parent, "menuBar", NULL, 0);

  menuPane1 = XmCreatePulldownMenu(menuBar, "menuPane1", NULL, 0);
  menuPane2 = XmCreatePulldownMenu(menuBar, "menuPane2", NULL, 0);

  b1 = XtCreateManagedWidget("b1", xmPushButtonWidgetClass, menuPane1, NULL,0);

  n = 0;
  XtSetArg(args[n], XmNsubMenuId, menuPane1);  n++;
  cascade1 = XmCreateCascadeButton(menuBar, "cascade1", args, n);
  XtManageChild(cascade1);

  n = 0;
  cascade2 = XmCreateCascadeButton(menuBar, "cascade2", args, n);
  XtManageChild(cascade2);

  n = 0;
  XtSetArg(args[n], XmNmenuHelpWidget, cascade2);  n++;
  XtSetValues(menuBar, args, n);

  XtAddCallback(b1, XmNactivateCallback, MenuCB, (XtPointer)MENU_QUIT);
  XtAddCallback(cascade2, XmNactivateCallback, MenuCB, (XtPointer)MENU_HELP);

  XtManageChild(menuBar);
}



/*--------------------------------------------------------------------*
 |                              Main                                  |
 *--------------------------------------------------------------------*/
int
main(int argc, char **argv)
{
   Widget           shell, mainWin, panedWin;
   Widget           keyboard, notebook;
   int              fn;
   Pixel            fg, bg;
   XGCValues        values;
   
   
   shell = XtVaAppInitialize(&context, APP_CLASS, NULL, 0, &argc, argv, fallback, NULL);

   XSetErrorHandler(MyErrorHandler);

   XtAddConverter(XtRString, XtRFloat, CvtStrToFloat, NULL, 0);
   appData   = GetAppResources(shell);
   
   mainWin   = XmCreateMainWindow(shell, "mainWin", NULL, 0);
   XtManageChild(mainWin);
   CreateMenuBar(mainWin);

   panedWin = XtVaCreateManagedWidget("panedWin",
				      xmPanedWindowWidgetClass, mainWin, NULL);

   keyboard = CreateKeyboard(panedWin);

   CreateScore(panedWin);
   BuildNoteTable(panedWin);

   notebook = CreateNotebook(panedWin);

   SetAppIcon(shell);

   XtRealizeWidget(shell);

   /* get the note GC */
   XtVaGetValues(appData->score, XmNforeground, &fg, XmNbackground, &bg, NULL);
   values.foreground = fg;
   values.background = bg;
   appData->noteGC = XtGetGC(appData->score, GCForeground | GCBackground, &values);

   SetActiveNote(appData->score, QUARTER);

   /* save the old bell values so that they can be restored. */
   GetBell(XtDisplay(shell));
   
   XtAppMainLoop(context);

   return 0;    /* make compiler happy */
}

