/*
**
**		Filename:	gtune.cxx
**
**		© (C) Copyright 1994 by Brian G. Neal.  All Rights Reserved.
**
**          A guitar tuning aid written in C++ for the Amiga.  
**    Features gadtools menus and gadgets, requesters, friendly audio
**    device use, ReadArgs() shell arguments, Workbench tool types,
**    an AppMenu, font flexibility, & keyboard shortcuts.
**
**          What it needs: more descriptive error messages when
**    failures occur.  I was too lazy to do this :^).
**
**		$Author: brian $
**		$Revision: 1.1 $
**		$Date: 94/09/13 19:37:35 $
**
*/
#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <libraries/gadtools.h>
#include <devices/audio.h>
#include <graphics/gfxbase.h>
#include <workbench/workbench.h>
#include <workbench/startup.h>
#include <clib/macros.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/dos_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/graphics_protos.h>
#include <clib/wb_protos.h>
#include <clib/icon_protos.h>
#include <clib/alib_protos.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#include <pragmas/gadtools_pragmas.h>
#include <pragmas/wb_pragmas.h>
#include <pragmas/icon_pragmas.h>
#include <string.h>
#include <stdlib.h>

/*==  #define constants  ==================================================*/

#define ARG_TEMPLATE "PUBSCREEN/K,LEFT/K/N,TOP/K/N,TUNING/K,NOGUI/S"

#define BASE_Y       10       /* starting Y offset for gadgets */

/*  shortcuts for menus  */
#define ITEM_CTC     (CHECKIT | MENUTOGGLE | CHECKED)
#define ITEM_CT      (CHECKIT | MENUTOGGLE)

#define NUM_GADS     3

#define GAD_NOTE     0           /* Gadget IDs  */
#define GAD_VOLUME   1
#define GAD_STRING   2

/*  Error Codes  */

#define OK                    0
#define ERR_NO_PUBSCREEN      1
#define ERR_NO_MEM            2
#define ERR_NO_FONT           3
#define ERR_NO_WINDOW         4
#define ERR_NO_AUDIO          5
#define ERR_NO_PORT           6
#define ERR_NO_AUDIO_CHAN     7
#define ERR_USAGE             8

#define MAX_STR               32       /* maximum chars in our strings */


/*==  Macros  =============================================================*/
/*==  This file's functions  ==============================================*/

int main(int argc, char *argv[]);
static void shutdown(char *errstr);
static void window_busy(struct Window *w);
static void window_unbusy(struct Window *w);
static int process_cli_args(void);
static int process_wb_args(char *argv[]);

/*==  Enums  ==============================================================*/
/*==  Structures  =========================================================*/
/*==  Unions  =============================================================*/
/*==  Typedefs  ===========================================================*/

typedef struct
{	
	ULONG kind;
	struct TagItem *tags;
	struct NewGadget ng;
}
	myNewGadget;

/*==  Global Variables  ===================================================*/

const static char version_str[] = "\0$VER: GTune 1.0 (9.9.94)";

struct Library *IntuitionBase = NULL;
struct Library *GfxBase = NULL;
struct Library *GadToolsBase = NULL;
struct Library *IconBase = NULL;
struct Library *WorkbenchBase = NULL;
extern struct Library *DOSBase;

/*  used for ignoring user input during about requester  */
static struct Requester req;
/*
 *   The pointer data for the 2.0 clock pointer.
 */
#define BUSY_SIZE 36
__chip UWORD busy_data[BUSY_SIZE] = {
   0x0000, 0x0000,
   0x0400, 0x07C0, 0x0000, 0x07C0, 0x0100, 0x0380, 0x0000, 0x07E0,
   0x07C0, 0x1FF8, 0x1FF0, 0x3FEC, 0x3FF8, 0x7FDE, 0x3FF8, 0x7FBE,
   0x7FFC, 0xFF7F, 0x7EFC, 0xFFFF, 0x7FFC, 0xFFFF, 0x3FF8, 0x7FFE,
   0x3FF8, 0x7FFE, 0x1FF0, 0x3FFC, 0x07C0, 0x1FF8, 0x0000, 0x07E0,
   0x0000, 0x0000,
};

static struct EasyStruct about_req =
{
	sizeof(struct EasyStruct), 0, (UBYTE *) "About GTune",
	(UBYTE *) "GTune v1.0\nby Brian Neal",
	(UBYTE *) "OK"
};

static struct EasyStruct nochan_req =
{
	sizeof(struct EasyStruct), 0, (UBYTE *) "GTune Error",
	(UBYTE *) "Could not allocate audio channel",
	(UBYTE *) "OK"
};


static struct TextAttr text_attr = 		/*  topaz 80  */
{ 
	(STRPTR) "topaz.font", 8, FS_NORMAL, FPF_ROMFONT | FPF_DESIGNED
};

static struct NewMenu new_menu[] =
{
/*   nm_Type      nm_Label         nm_CommKey nm_Flags nm_MutEx nm_UserData */

   { NM_TITLE, "Project",              NULL,      0,     0,        NULL },

#define TUNE_ITEM    0
   { NM_ITEM,     "Tuning",            NULL,      0,     0,        NULL },
   { NM_SUB,         "Standard",       "S",   ITEM_CTC, ~1,        NULL },
   { NM_SUB,         "Down Half Step", "D",   ITEM_CT,  ~2,        NULL },
   { NM_SUB,         "Down Full Step", "F",   ITEM_CT,  ~4,        NULL },

#define HIDE_ITEM    1
   { NM_ITEM,     "Hide",              "H",       0,     0,        NULL },

#define ABOUT_ITEM   2
   { NM_ITEM,     "About",             NULL,      0,     0,        NULL },

#define QUIT_ITEM    3
   { NM_ITEM,     "Quit",              "Q",       0,     0,        NULL },

   { NM_END,   NULL,                   NULL,      0,     0,        NULL }
};

static char *note_text[3][7] = 
{
   { "E",  "B",  "G",  "D",  "A",  "E",  " " },    /* standard tuning */
   { "Eb", "Bb", "Gb", "Db", "Ab", "Eb", " " },    /* down 1/2 step   */
   { "D",  "A",  "F",  "C",  "G",  "D",  " " }     /* down full step  */
};  

static char *title_text[3] =     /* window titles */
{
   "GTune - Standard", 
   "GTune - Down 1/2",
   "GTune - Down Full"
};

/*  current note playing parameters  */

static int tune_index = 0; /* tuning index; 0=standard, 1=down 1/2, 2=down 1  */
static int note_index = 6; /* note index; 0=1st string, 5=6th string,         */
                           /*             6=no note                           */
static UWORD volume = 64;  /* current note volume (0-64, 64 is the loudest)   */

#define WAVE_BYTES 4
__chip BYTE waveform[WAVE_BYTES] = { 0, 127, 0, -128 };

/*
**       The following tables were calculated by using a formula I found
**    on the old 1.2 Amiga Extras Disk in the BasicDemos drawer.  There
**    is this pretty neat music demo called "Music".  It contains the
**    following formula for computing frequencies:
**
**       REM F#() contains frequencies for notes in the diatonic scale
**       REM octave 0, note A = F#(12) = 55 Hz
**
**       Log2of27.5# = LOG(27.5#)/LOG(2#)
**       FOR X=1 TO 88
**          F#(X) = 2^(Log2of27.5# + X/12)
**       NEXT X
**
**       I then used a spreadsheet (ASC, the Amiga port of the Unix SC) 
**    to come up with the following periods, which are given in nanosecond
**    increments (279.365 ns for NTSC).  The formula used is:
**
**       period = round(clock_factor * 1/freq)
**
**       where clock_factor == 3579545 for NTSC (3579545 == 1/279.365e-9) 
**         and clock_factor == 3546895 for PAL
*/
static ULONG ntsc_tones[3][6] =
{            /*                       STRING                         */
             /*    1st      2nd      3rd      4th      5th      6th  */
             /*   -------------------------------------------------- */
/* standard  */ { 10859,   14496,   18263,   24378,   32541,   43437 },
/* down 1/2  */ { 11505,   15357,   19349,   25828,   34476,   46020 },
/* down full */ { 12189,   16271,   20500,   27364,   36526,   48757 }
}; 

static ULONG pal_tones[3][6] =
{            /*                       STRING                         */
             /*    1st      2nd      3rd      4th      5th      6th  */
             /*   -------------------------------------------------- */
/* standard  */ { 10760,   14363,   18097,   24156,   32244,   43041 },
/* down 1/2  */ { 11400,   15217,   19173,   25592,   34162,   45601 },
/* down full */ { 12078,   16122,   20313,   27114,   36193,   48312 }
}; 

/*static ULONG *tones = &ntsc_tones[0][0];*/
static ULONG (*tones)[6] = &ntsc_tones[0];


static char *string_labels[] = { "_1", "_2", "_3", "_4", "_5", "_6", "_Off", NULL };

static struct TagItem text_tags[]   = { {GTTX_Text, (ULONG) " "}, 
                                        {GTTX_Border, TRUE}, 
                                        {TAG_END, 0} };
static struct TagItem note_tags[2]  = { {GTTX_Text, 0}, {TAG_END, 0} };

static struct TagItem string_tags[] = { {GTMX_Labels, (ULONG) string_labels}, 
                                        {GTMX_Active, 6},
                                        {GTMX_Spacing, INTERHEIGHT},
                                        {GT_Underscore, '_'},
                                        {TAG_END, 0} };

static struct TagItem vol_tags[]    = { {GTSL_Min, 0}, {GTSL_Max, 64},
                                        {GTSL_Level, 64}, 
                                        {GA_Immediate, TRUE},
                                        {GA_RelVerify, TRUE},
                                        {PGA_Freedom, LORIENT_VERT},
                                        {GT_Underscore, '_'},
                                        {TAG_END, 0} };


static myNewGadget new_gads[ NUM_GADS ] =
{
	{ TEXT_KIND, text_tags, { 0, BASE_Y, 40, 20, (UBYTE *) "Note", &text_attr,
			GAD_NOTE, PLACETEXT_BELOW, NULL, NULL } },
	{ SLIDER_KIND, vol_tags, { 0, BASE_Y, 20, 80, (UBYTE *) "_Volume", 
			&text_attr, GAD_VOLUME, PLACETEXT_BELOW, NULL, NULL } },
	{ MX_KIND, string_tags, { 0, BASE_Y, 15, 80, NULL, &text_attr, GAD_STRING, 
         PLACETEXT_RIGHT, NULL, NULL } },
};


static struct IntuiText string_itext[] = 
{ 
   { 2, 0, JAM1, 0, BASE_Y, NULL, (UBYTE *) "String", &string_itext[1] },
   { 2, 0, JAM1, 0, BASE_Y, NULL, (UBYTE *) "_", NULL }
};


static LONG nw_LeftEdge = 10;     /*  initial window positions  */
static LONG nw_TopEdge  = 10;

/*  name of Public screen to open on  */
static char pscreen[MAX_STR+1] = "Workbench";

/*  TRUE when user wants to hide user interface  */
static BOOL hide_gui = FALSE;    

  
/*=========================================================================*/
/*==  Classes  ============================================================*/
/*=========================================================================*/

/*:::::  The library class  :::::::::::::::::::::::::::::::::::::::::::::::*/

class library
{
      struct Library **base;
      char *name;
      UBYTE rev;
   public:
      library(char *libname, UBYTE librev, struct Library **libbase);
      BOOL open(void); 
      void close(void);
};

/*:::::  The Audio Device class  ::::::::::::::::::::::::::::::::::::::::::*/

class audio_dev
{
      struct MsgPort *aport;
      struct IOAudio *aio;       /* for playing notes    */
      struct IOAudio *actrl;     /* for controlling above request  */
      BOOL dev_open;
      BOOL note_playing;
   public:
      audio_dev(void) 
      { 
         note_playing = dev_open = FALSE; 
         aport = NULL;
         aio = actrl = NULL;
      }
      int open(ULONG &sigmask);
      void close(void);
      int play_note(ULONG note, UWORD volume);
      void stop_note(void);
      void adjust_volume(UWORD volume);
      void i_was_robbed(void);
};

/*:::::  The Window class  ::::::::::::::::::::::::::::::::::::::::::::::::*/

class window
{
      struct Window *w;
      void *visual_info;
      struct Menu *menu_strip;
      int note_gad_y, string_gad_y, string_txt_y;
      struct Gadget *glist;
      struct Gadget *gads[ NUM_GADS ];
      BOOL process_menu(UWORD code);
      void process_gads(struct Gadget *gad, UWORD code); 
      void process_key(UWORD code);
   public:
      window(void)
      { 
         int i; 
            w = NULL; visual_info = NULL; menu_strip = NULL; glist = NULL;
            for (i = 0; i < NUM_GADS; ++i) gads[i] = NULL;
            note_gad_y = string_gad_y = string_txt_y = BASE_Y;
      }
      int open(char *pscreen, ULONG &sigmask);
      void close(void);
      BOOL process(void);
      void display_off(void);
};


/*:::::  The AppMenu class  :::::::::::::::::::::::::::::::::::::::::::::::*/

#define MAX_APPMENU 2
class appmenu
{
      struct MsgPort *app_port;
      struct AppMenuItem *items[MAX_APPMENU];
      int num_items;
   public:
      appmenu(void)
      { 
         int i;
            app_port = NULL;
            for (i = 0; i < MAX_APPMENU; ++i) items[i] = NULL;
            num_items = 0;
      }
      int init(void)
      {
         if (app_port = CreateMsgPort())
            return 1L << (app_port -> mp_SigBit);
         else
            return 0;
      }
      int add(char *text, ULONG id);
      int process(void);
      void free(void);
};
           

/*:::::  The Library Class Methods  :::::::::::::::::::::::::::::::::::::::*/

library::library(char *libname, UBYTE librev, struct Library **libbase)
{
      base = libbase;
      name = libname;
      rev  = librev;
}

void library::close(void)
{
   if (*base) CloseLibrary(*base); 
   *base = NULL; 
}
      
BOOL library::open(void)
{
      if (*base == NULL) *base = OpenLibrary((UBYTE *) name, (ULONG) rev);
      return (*base) ? TRUE : FALSE;
}


/*:::::  The Window Class Methods  ::::::::::::::::::::::::::::::::::::::::*/


int window::open(char *pscreen, ULONG &sigmask)
{
   struct Screen *screen;
   int deltay, left_edge, fonty, i, vol_len;
   struct Gadget *prev_gad;
   int nwWidth, nwHeight;

      if (w)                  /*  are we already open?  */
      {
         WindowToFront(w);    /*  if so, just pop us to front  */
         return OK;
      }

      if (pscreen && *pscreen == '\0') pscreen = NULL;
      if (!(screen = LockPubScreen((UBYTE *) pscreen)))	
      {
         /*  No public screen... try the Workbench  */
         if (!(screen = LockPubScreen(NULL))) return ERR_NO_PUBSCREEN;
      }

      if (!(visual_info = GetVisualInfoA(screen, NULL))) return ERR_NO_MEM;

      deltay    = screen -> BarHeight;
      left_edge = screen -> WBorLeft + 10;
      fonty     = screen -> Font -> ta_YSize;

      if (!(prev_gad = CreateContext(&glist))) return ERR_NO_MEM;

      new_gads[GAD_NOTE].ng.ng_TopEdge = (WORD) (note_gad_y + deltay);
      new_gads[GAD_NOTE].ng.ng_Width = 
         MAX(new_gads[GAD_NOTE].ng.ng_Width,
             TextLength(&(screen -> RastPort), 
                        (char *) new_gads[GAD_NOTE].ng.ng_GadgetText,
                        strlen((char *) new_gads[GAD_NOTE].ng.ng_GadgetText)));
      new_gads[GAD_NOTE].ng.ng_Height = (WORD) (fonty + 2 * INTERHEIGHT);

      vol_len = TextLength(&(screen -> RastPort), 
                       (char *) new_gads[GAD_VOLUME].ng.ng_GadgetText,
                       strlen((char *) new_gads[GAD_VOLUME].ng.ng_GadgetText));
      new_gads[GAD_VOLUME].ng.ng_LeftEdge = (WORD) (left_edge + vol_len / 2);
      new_gads[GAD_VOLUME].ng.ng_TopEdge  = (WORD) (new_gads[GAD_NOTE].ng.ng_TopEdge 
                                          + new_gads[GAD_NOTE].ng.ng_Height  
                                          + fonty + INTERHEIGHT);
      new_gads[GAD_VOLUME].ng.ng_Width  = 20;
      new_gads[GAD_VOLUME].ng.ng_Height = (WORD) (6 * (fonty + INTERHEIGHT) 
                                          + BASE_Y
                                          - new_gads[GAD_NOTE].ng.ng_Height);

      new_gads[GAD_NOTE].ng.ng_LeftEdge = (WORD) 
            ((new_gads[GAD_VOLUME].ng.ng_Width - new_gads[GAD_NOTE].ng.ng_Width)
            / 2 + new_gads[GAD_VOLUME].ng.ng_LeftEdge);

      string_itext[0].LeftEdge = (WORD) 
                  (new_gads[GAD_VOLUME].ng.ng_LeftEdge
                     + MAX(new_gads[GAD_VOLUME].ng.ng_Width, vol_len)
                     + 2 * INTERWIDTH);
      string_itext[0].TopEdge   = (WORD) (string_txt_y + deltay);
      string_itext[0].ITextFont = screen -> Font;

      string_itext[1].LeftEdge  = string_itext[0].LeftEdge;
      string_itext[1].TopEdge   = string_itext[0].TopEdge ;
      string_itext[1].ITextFont = string_itext[0].ITextFont;

      new_gads[GAD_STRING].ng.ng_TopEdge  = (SHORT) (string_gad_y + deltay 
                                          + fonty + INTERHEIGHT);
      new_gads[GAD_STRING].ng.ng_LeftEdge = (SHORT) (string_itext[0].LeftEdge
                                             + INTERWIDTH);

      for (i = 0; i < NUM_GADS; ++i)
      {
         new_gads[i].ng.ng_VisualInfo	= (APTR) visual_info;
         new_gads[i].ng.ng_TextAttr = screen -> Font;
         gads[i] = prev_gad = 
            CreateGadgetA(new_gads[i].kind, prev_gad, &new_gads[i].ng, 
                                                         new_gads[i].tags);
         if (!prev_gad) return ERR_NO_MEM;
      } 

      /*  Layout & setup menu  */

      for (i = 0; i < 3; ++i)
            new_menu[2 + i].nm_Flags = (UWORD) ((tune_index == i) ? ITEM_CTC 
                                                                  : ITEM_CT);

      if (!(menu_strip = CreateMenusA(new_menu, NULL))) return ERR_NO_MEM;
      if (!LayoutMenusA(menu_strip, visual_info, NULL)) return ERR_NO_FONT;

      /*  determine coordinates & size  */

      nwWidth    = MIN(screen -> Width, 4 * vol_len);
      nwHeight   = MIN(screen -> Height, 
                       MAX(gads[GAD_VOLUME] -> TopEdge 
                              + gads[GAD_VOLUME] -> Height 
                              + 2 * INTERHEIGHT + fonty,
                           7 * fonty + 2 * deltay + BASE_Y)
                       );

      /*  open on the user's public screen  */

      w = OpenWindowTags(NULL, WA_Width, nwWidth, WA_Height, nwHeight,
                              WA_Left, nw_LeftEdge, WA_Top, nw_TopEdge,
                              WA_Gadgets, glist,
                              WA_PubScreen, screen,
                              WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_MENUPICK |
                                        SLIDERIDCMP | MXIDCMP | TEXTIDCMP |
                                        IDCMP_REFRESHWINDOW | IDCMP_VANILLAKEY,
                              WA_Flags, WFLG_DRAGBAR | WFLG_DEPTHGADGET |
                                        WFLG_CLOSEGADGET | WFLG_SMART_REFRESH |
                                        WFLG_ACTIVATE,
                              WA_Title, (ULONG) title_text[tune_index], 
                              WA_MinWidth, nwWidth,
                              WA_MinHeight, nwHeight,
                              TAG_END);
      if (w)
      {
         GT_RefreshWindow(w, NULL);
         SetMenuStrip(w, menu_strip);
         PrintIText(w -> RPort, &string_itext[0], 0, 0);
         sigmask = 1L << (w -> UserPort -> mp_SigBit);
      }

      UnlockPubScreen(NULL, screen);
      return (w) ? OK : ERR_NO_WINDOW;
}


void window::close(void)
{
   struct Message *msg;
   int i;

      if (w) 
      {
         /*  record window coordinates for the next open  */
         nw_LeftEdge = w -> LeftEdge;
         nw_TopEdge  = w -> TopEdge;

         while (msg = GetMsg(w -> UserPort)) ReplyMsg(msg);
         CloseWindow(w);
      }
      if (menu_strip)
      {
         ClearMenuStrip(w);
         FreeMenus(menu_strip);
      }
      if (glist)       FreeGadgets(glist);
      if (visual_info) FreeVisualInfo(visual_info);

      w           = NULL;
      visual_info = NULL;
      menu_strip  = NULL;
      glist       = NULL;
      for (i = 0; i < NUM_GADS; ++i) gads[i] = NULL;
}


BOOL window::process(void)
{
   BOOL quit = FALSE;
   struct IntuiMessage *msg;
   ULONG msg_class;
   UWORD code;

      /*
      **  Choosing Hide from our menu will close the window,
      **  thus we also check our window pointer in the message
      **  loop below:
      */
      while (w && (msg = GT_GetIMsg(w -> UserPort)))
      {
         msg_class = msg -> Class;
         code  = msg -> Code;
         GT_ReplyIMsg(msg);

         switch (msg_class)
         {
            case IDCMP_CLOSEWINDOW:
               quit = TRUE;
               break;

            case IDCMP_MENUPICK:
               quit = process_menu(code);
               break;

            case IDCMP_GADGETUP:
            case IDCMP_MOUSEMOVE:
            case IDCMP_GADGETDOWN:
               process_gads((struct Gadget *) (msg -> IAddress), code);
               break;

            case IDCMP_VANILLAKEY:
               process_key(code);
               break;

            case IDCMP_REFRESHWINDOW:
               GT_BeginRefresh(w);
               PrintIText(w -> RPort, &string_itext[0], 0, 0);
               GT_EndRefresh(w, TRUE);
               break;

            default:
               break;
         }
      }
      return quit;
}


BOOL window::process_menu(UWORD code)
{
   extern audio_dev audio;
   UWORD menu_num, item_num, sub_num;
   struct MenuItem *item;
   BOOL quit = FALSE;

      menu_num = code;
      while (menu_num != MENUNULL && !quit)
      {
         item_num = (UWORD) ITEMNUM(menu_num);
         sub_num  = (UWORD) SUBNUM(menu_num);

         if (item_num == QUIT_ITEM)
            quit = TRUE;
         else if (item_num == ABOUT_ITEM)
         {
            window_busy(w);
            EasyRequestArgs(w, &about_req, NULL, NULL);
            window_unbusy(w);
         }
         else if (item_num == TUNE_ITEM)
         {
            tune_index = sub_num;
            SetWindowTitles(w, (UBYTE *) title_text[tune_index], (UBYTE *) -1);
            if (note_index != 6)
            {
               audio.play_note(tones[tune_index][note_index], volume);
               note_tags[0].ti_Data = (ULONG) note_text[tune_index][note_index];
               GT_SetGadgetAttrsA(gads[GAD_NOTE], w, NULL, note_tags);
            }
         }
         else if (item_num == HIDE_ITEM)
         {
            audio.stop_note();
            close();       /*  close the window  */
            break;         /*  would be dangerous if there were  */
                           /*  more menu selections              */
         }
         
         item = ItemAddress(menu_strip, menu_num);
         menu_num = item -> NextSelect;
      }
      return quit;
}


void window::process_gads(struct Gadget *gad, UWORD code)
{
   extern audio_dev audio;

      if (gad -> GadgetID == GAD_VOLUME)
      {
         volume = code;
         if (note_index != 6)
            audio.adjust_volume(volume);
      }
      else
      {
         if (code == 6)
         {
            audio.stop_note();
            note_index = 6;
            note_tags[0].ti_Data = (ULONG) note_text[tune_index][note_index];
            GT_SetGadgetAttrsA(gads[GAD_NOTE], w, NULL, note_tags);
         }
         else
         {
            if (OK == audio.play_note(tones[tune_index][code], volume))
            {
               note_index = code;
               note_tags[0].ti_Data = (ULONG) note_text[tune_index][note_index];
               GT_SetGadgetAttrsA(gads[GAD_NOTE], w, NULL, note_tags);
            }
            else  /* failed to get channel, reset gadget imagery */
            {
               GT_SetGadgetAttrs(gads[GAD_STRING], w, NULL, GTMX_Active, 
                                    note_index, TAG_END);
               window_busy(w);
               EasyRequestArgs(w, &nochan_req, NULL, NULL);
               window_unbusy(w);
            }
         }
       }
}


void window::process_key(UWORD code)
{
   extern audio_dev audio;
   BOOL adjust_vol = FALSE, new_note = TRUE;
   int r, old_index;

      old_index = note_index;
      switch (code)
      {
         case 'v':
            if (++volume > 64) volume = 64;
            adjust_vol = TRUE;
            break;

         case 'V':
            if (volume > 0) --volume;
            adjust_vol = TRUE;
            break;

         case 's':
            if (++note_index > 6) note_index = 0;
            break;

         case 'S':
            if (--note_index < 0) note_index = 6;
            break;

         case 'O':
         case 'o':
         case '0':
            note_index = 6;
            break;

         default:
            if (code >= '1' && code <= '6')
               note_index = code - '0' - 1;
            else
               new_note = FALSE;
            break;
      }

      if (adjust_vol)
      {
         if (note_index != 6) audio.adjust_volume(volume);
         GT_SetGadgetAttrs(gads[GAD_VOLUME], w, NULL, GTSL_Level,
                              volume, TAG_END);
      }
      else if (new_note)
      {
         r = OK;
         if (note_index == 6)
            audio.stop_note();
         else
            r = audio.play_note(tones[tune_index][note_index], volume);

         if (r == OK)
         {
            note_tags[0].ti_Data = (ULONG) note_text[tune_index][note_index];
            GT_SetGadgetAttrsA(gads[GAD_NOTE], w, NULL, note_tags);
            GT_SetGadgetAttrs(gads[GAD_STRING], w, NULL, GTMX_Active, 
                                 note_index, TAG_END);
         }
         else
         {
            note_index = old_index;
            window_busy(w);
            EasyRequestArgs(w, &nochan_req, NULL, NULL);
            window_unbusy(w);
          }
      }
}


void window::display_off(void)
{
      if (w)
      {
         note_index = 6;
         GT_SetGadgetAttrs(gads[GAD_STRING], w, NULL, GTMX_Active, 
                              note_index, TAG_END);
      }
}


/*:::::  The Audio Device Methods  ::::::::::::::::::::::::::::::::::::::::*/


int audio_dev::open(ULONG &sigmask)
{
   
      if (aport = CreateMsgPort())
      {
         aio = (struct IOAudio *) AllocMem(sizeof(struct IOAudio), 
                                             MEMF_PUBLIC | MEMF_CLEAR);
         if (aio)
         {
            aio -> ioa_Request.io_Message.mn_ReplyPort = aport;

            if (dev_open = (BOOL) !OpenDevice((UBYTE *) AUDIONAME, 0L, 
                                          (struct IORequest *) aio, 0L))
            {
               actrl = (struct IOAudio *) AllocMem(sizeof(struct IOAudio), 
                                                   MEMF_PUBLIC | MEMF_CLEAR);
               if (!actrl) return ERR_NO_MEM;
               *actrl = *aio;

               /*  set up our note table for PAL or NTSC  */

               if (((struct GfxBase *) GfxBase) -> DisplayFlags & PAL)
                  tones = &pal_tones[0];
               else
                  tones = &ntsc_tones[0];

               sigmask = 1L << aport -> mp_SigBit;
               return OK;
            }
            else
               return ERR_NO_AUDIO;
         }
         else
            return ERR_NO_MEM;
      }
      else 
         return ERR_NO_PORT;
}


void audio_dev::close(void)
{
      stop_note();
      if (dev_open) CloseDevice((struct IORequest *) aio);
      if (aport) DeleteMsgPort(aport);
      if (actrl) FreeMem(actrl, sizeof(struct IOAudio));
      if (aio) FreeMem(aio, sizeof(struct IOAudio));

      dev_open = note_playing = FALSE;
      aport = NULL;
      aio = actrl = NULL;
}


void audio_dev::stop_note(void)
{
      if (note_playing)
      {
         actrl -> ioa_Request.io_Command = ADCMD_FINISH;
         actrl -> ioa_Request.io_Flags   = ADIOF_SYNCCYCLE | IOF_QUICK;
         BeginIO((struct IORequest *) actrl);
         /*
          * NOTE: since I/O Quick bit is set, audio.device does not
          * reply to our FINISH message.  Thus we don't have to wait for it
          * to come back and retrieve it from the message port.  But, we
          * still have to wait for the WRITE message to finish up.
          */
         WaitPort(aport);
         GetMsg(aport);
         note_playing = FALSE;

         /*  now free up our channel  */

         actrl -> ioa_Request.io_Command = ADCMD_FREE;
         actrl -> ioa_Request.io_Flags   = IOF_QUICK;
         BeginIO((struct IORequest *) actrl);
      }
}

/*  clean up after our channel was stolen  */

void audio_dev::i_was_robbed(void)
{
      note_playing = FALSE;
      GetMsg(aport);
}


int audio_dev::play_note(ULONG note, UWORD volume)
{
   UBYTE channels[4] = { 1, 2, 4, 8 };

      stop_note();

      /*  first try to allocate a channel  */

      actrl -> ioa_Request.io_Command = ADCMD_ALLOCATE;
      actrl -> ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
      actrl -> ioa_AllocKey = 0;
      actrl -> ioa_Data = channels;
      actrl -> ioa_Length = 4;

      BeginIO((struct IORequest *) actrl);

		if (actrl -> ioa_Request.io_Error != 0) /* failed to get a channel */
         return ERR_NO_AUDIO_CHAN;
      else
      {
         /* copy needed fields from our control message  */

         aio -> ioa_AllocKey = actrl -> ioa_AllocKey;
         aio -> ioa_Request.io_Unit = actrl -> ioa_Request.io_Unit;

         /*  now play note  */

         aio -> ioa_Request.io_Command = CMD_WRITE;
         aio -> ioa_Request.io_Flags   = ADIOF_PERVOL;
         aio -> ioa_Data               = (UBYTE *) waveform;
         aio -> ioa_Length             = WAVE_BYTES;
         aio -> ioa_Period             = (UWORD) (note / WAVE_BYTES);
         aio -> ioa_Volume             = volume;
         aio -> ioa_Cycles             = 0;  /* repeat forever */

         BeginIO((struct IORequest *) aio);
         note_playing = TRUE;
         return OK;
      }
}


void audio_dev::adjust_volume(UWORD volume)
{
      if (note_playing)
      {
         actrl -> ioa_Request.io_Command = ADCMD_PERVOL;
         actrl -> ioa_Request.io_Flags   = ADIOF_SYNCCYCLE | IOF_QUICK;
         actrl -> ioa_Period             = (UWORD) 
                              (tones[tune_index][note_index] / 4);
         actrl -> ioa_Volume             = volume;
         BeginIO((struct IORequest *) actrl);
         /*
          * NOTE: since I/O Quick bit is set, audio.device does not
          * reply to our message.  Thus we don't have to wait for it
          * to come back and retrieve it from the message port.
          */
      }
}


/*:::::  The AppMenu Methods  :::::::::::::::::::::::::::::::::::::::::::::*/

int appmenu::add(char *text, ULONG id)
{
   struct AppMenuItem *item;

      if (num_items >= MAX_APPMENU) return 0;
      item = AddAppMenuItemA(id, 0L, (UBYTE *) text, app_port, NULL);
      if (item)
      {
         items[num_items++] = item;
         return 1;
      }
      else
         return 0;
}

void appmenu::free(void)
{
   int i;
   struct Message *msg;

      if (app_port)
      {
         for (i = 0; i < num_items; ++i)
         {
            RemoveAppMenuItem(items[i]);
            items[i] = NULL;
         }
         while (msg = GetMsg(app_port)) ReplyMsg(msg);
         DeleteMsgPort(app_port);
      }
      num_items = 0;
      app_port = NULL;
}


int appmenu::process(void)
{
   struct AppMessage *appmsg;
   int id = -1;

      while (appmsg = (struct AppMessage *) GetMsg(app_port))
      {
         id = (int) appmsg -> am_ID;
         ReplyMsg((struct Message *) appmsg);
      }
      return id;
}


/*==  Global Objects  =====================================================*/

library intlib("intuition.library", 37, &IntuitionBase),
        gfxlib("graphics.library", 0, &GfxBase),
        gadlib("gadtools.library", 37, &GadToolsBase),
        iconlib("icon.library", 0, &IconBase),
        wblib("workbench.library", 37, &WorkbenchBase);

window win;
audio_dev audio;
appmenu wbmenu;

/*==  Variables visible to this file only  ================================*/

static BOOL fromWB = FALSE;


/*=========================================================================*/
/*==  F U N C T I O N S  ==================================================*/
/*=========================================================================*/

int main(int argc, char *argv[])
{
   ULONG winmask, menumask, audiomask, waitmask, bits;
   int r;
   BOOL done;

      fromWB = (BOOL) (argc == 0);

      if (!intlib.open())  shutdown("Need V37 of Intuition\n");
      if (!gfxlib.open())  shutdown("Could not open graphics library\n");
      if (!gadlib.open())  shutdown("Could not open gadtools library\n");
      if (!iconlib.open()) shutdown("Could not open icon library\n");
      if (!wblib.open())   shutdown("Could not open workbench library\n");

      if (fromWB)
         r = process_wb_args(argv);
      else
         r = process_cli_args();

      if (r != OK) shutdown("Argument Error\n");
      if (0 == (menumask = wbmenu.init())) shutdown("WB menu error\n");

      if (hide_gui)
         winmask = 0;
      else if (OK != win.open(pscreen, winmask))
         shutdown("No window\n");

      if (OK != audio.open(audiomask)) shutdown("Could not open audio device\n");
      if (0 == wbmenu.add("GTune", 0)) shutdown("Could not add WB menu item\n");

      done = FALSE;
      while (!done)
      {
         waitmask = menumask | winmask | audiomask | SIGBREAKF_CTRL_C;
         bits = Wait(waitmask);
         if (bits & audiomask)
         {
            audio.i_was_robbed();
            win.display_off();
         }
         if (bits & winmask) done = win.process();
         if (bits & menumask) 
         {
            wbmenu.process();
            if (OK != win.open(pscreen, winmask)) shutdown("No window\n");
         }
         done = (BOOL) (done || (bits & SIGBREAKF_CTRL_C));
      }

      shutdown(NULL);
      return 0;      /* NOT REACHED */
}


static void shutdown(char *errstr)
{
   int exitcode;

      wbmenu.free();
      audio.close();
      win.close();
      wblib.close();
      iconlib.close();
      gadlib.close();
      gfxlib.close();
      intlib.close();

      if (errstr && *errstr)
      {
         if (!fromWB) Write(Output(), errstr, strlen(errstr));
         exitcode = 20;
      }
      else
         exitcode = 0;

      exit(exitcode);
}

   

/*
 *   This routine sets the busy pointer and blocks out all input from the
 *   user.  Based on SetWindowBusy() by Willy Langeveld.  Thanks Willy!
 */
static void window_busy(struct Window *w)
{
   if (w)
	{
		SetPointer(w, busy_data, 16, 16, -6, 0);
/*
 * Comments from Willy:
 *   Set up an invisible requester to block IDCMP input for "window".
 *   InitRequester sets the position of the requester to window coordinates
 *   (0, 0), width 0 and height 0. The "Flags" field is also set to zero.
 *   This code is valid under both 1.3 and 2.0, according to CATS.
 */
      InitRequester(&req);
      Request(&req, w);
   }
}

/*
 *   This routine clears the busy pointer and resumes receiving input from the
 *   user.  Based on ClrWindowBusy() by Willy Langeveld.  Thanks Willy!
 */
static void window_unbusy(struct Window *w)
{
   if (w)
	{
       EndRequest(&req, w);
       ClearPointer(w);
   }
}


static int process_cli_args(void)
{
	LONG left = 10, top = 10;
	LONG results[] = { 0,              /*  pubscreen     */
							 (LONG) &left,   /*  left *        */
							 (LONG) &top,    /*  top *         */
							 0,              /*  tuning        */
							 0,              /*  nogui         */
						  };
	struct RDArgs *rda;


		if (rda = ReadArgs(ARG_TEMPLATE, results, NULL))
		{
			if (results[0]) strncpy(pscreen, (char *) results[0], MAX_STR);

			nw_LeftEdge  = * (LONG *) results[1];		/* aint C cool?  */
			nw_TopEdge   = * (LONG *) results[2];

			if (results[3])
         {
            if (0 == stricmp("DOWNHALF", (char *) results[3]))
               tune_index = 1;
            else if (0 == stricmp("DOWNFULL", (char *) results[3]))
               tune_index = 2;
            else                    /*  assume "STANDARD"  */
               tune_index = 0;
         }
         hide_gui = (BOOL) ((results[4] == 0) ? FALSE : TRUE);

			FreeArgs(rda);
			return OK;
		}
		else
			return ERR_USAGE;
}


static int process_wb_args(char *argv[])
{
   struct WBStartup  *arg_msg;
   struct WBArg      *wb_arg;
   struct DiskObject *icon_ptr;
   char					*str;

      arg_msg = (struct WBStartup *) argv;
      wb_arg  = arg_msg -> sm_ArgList;

      if (!(icon_ptr = GetDiskObject((UBYTE *) wb_arg -> wa_Name)))      
			return ERR_NO_MEM; /*  ??  */ 

		if (str = (char *)
			FindToolType((UBYTE **) icon_ptr -> do_ToolTypes, (UBYTE *) "PUBSCREEN"))
		{
			strncpy(pscreen, str, MAX_STR);
		}

      if (str = (char *) 
			FindToolType((UBYTE **) icon_ptr -> do_ToolTypes, (UBYTE *) "LEFT"))
		{
         nw_LeftEdge = atoi(str);
		}

      if (str = (char *) 
			FindToolType((UBYTE **) icon_ptr -> do_ToolTypes, (UBYTE *) "TOP"))
		{
         nw_TopEdge = atoi(str);
		}

		if (str = (char *)
			FindToolType((UBYTE **) icon_ptr -> do_ToolTypes, (UBYTE *) "TUNING"))
		{
         if (MatchToolValue((UBYTE *) str, (UBYTE *) "DOWNHALF"))
            tune_index = 1;
         else if (MatchToolValue((UBYTE *) str, (UBYTE *) "DOWNFULL"))
            tune_index = 2;
         else                    /*  assume "STANDARD"  */
            tune_index = 0;
		}

      if (FindToolType((UBYTE **) icon_ptr -> do_ToolTypes, (UBYTE *) "NOGUI"))
         hide_gui = TRUE;
      else
         hide_gui = FALSE;
		
      FreeDiskObject(icon_ptr);
		return OK;
}


/*==  End of gtune.cxx  ===================================================*/
