/*

Subj:  Guitarist's Helper

Fellow guitarists/bassists:

I wrote a program that prints a guitar fretboard diagram, and
labels notes or patterns according command-line args.  I found it
helpful in learning and scales and patterns.

The user can specify scale notes directly, or supply a root and
formula for a scale.  Positions on the fretboard diagram may be labled
with a user-defined character if desired (this is good for learning
patterns as opposed to actual note names).  Here's a sample output:

synapse /betty/b3/fcg 17> fretbd -i G# 2 2 1 2 2 2

Scale = G# A# C C# D# F G
    +       +       +       +       +          + +          +       +       +
-||-F-|---|-G-|-G#|---|-A#|---|-C-|-C#|---|-D#|---|-F-|---|-G-|-G#|---|-A#|---|
-||-C-|-C#|---|-D#|---|-F-|---|-G-|-G#|---|-A#|---|-C-|-C#|---|-D#|---|-F-|---|
G||-G#|---|-A#|---|-C-|-C#|---|-D#|---|-F-|---|-G-|-G#|---|-A#|---|-C-|-C#|---|
-||-D#|---|-F-|---|-G-|-G#|---|-A#|---|-C-|-C#|---|-D#|---|-F-|---|-G-|-G#|---|
-||-A#|---|-C-|-C#|---|-D#|---|-F-|---|-G-|-G#|---|-A#|---|-C-|-C#|---|-D#|---|
-||-F-|---|-G-|-G#|---|-A#|---|-C-|-C#|---|-D#|---|-F-|---|-G-|-G#|---|-A#|---|

This is my first posting of code to the net; any comments or
suggestions are welcome.  Please use e-mail replies where appropriate.

- Yours truly

*/
/* ********************************************************************** */
/*
 * FILE:            fretbd.2.c
 * AUTHOR:          Frank C. Guida              Philips Laboratories
 *                  (fcg@philabs.philips.com)   Briarcliff Manor, NY 10510
 *
 * DATE:            Fri Dec 22 10:49:29 1989
 *
 * Please acknowledge the author in any reproduction or modification.
 *
 * SYNOPSIS:        fretbd [-h] [-s] [-m char]
 *                         [-i root interval_list] | [-n note_list]
 *
 * DESCRIPTION:     fretbd prints a guitar fretboard (20 frets)
 *                  and labels notes according to the command-line
 *                  args.  Notes may be entered in upper or lower
 *                  case; use b or # to indicate accidentals.
 *
 * OPTION SUMMARY:  (no args):  print blank fretboard.
 *                         -h:  print this message.
 *                         -s:  use sharps instead of flats as the
 *                              dflt_note_type (use with -i option).
 *                         -m:  use char to mark note positions as
 *                              opposed to letter names.
 *                         -i:  construct scale using specified root
 *                              and intervals (in half-steps).
 *                         -n:  construct scale using specified notes.
 *
 * EXAMPLE:         to print the F#maj scale -
 *                  host> fretbd -i F# 2 2 1 2 2 2
 *
 */
/* ********************************************************************** */

#include  <stdio.h>
#include  <ctype.h>
#include  <string.h>

#define  HELP_MESSAGE "\
 Use:  fretbd [-h] [-s] [-m char] [-i root interval_list] | [-n note_list]\n\n\
 Option Summary:  (no args):  print blank fretboard.\n\
                         -h:  print this message.\n\
                         -s:  use sharps instead of flats as the\n\
                              dflt_note_type (use with -i option).\n\
                         -m:  use char to mark note positions as\n\
                              opposed to letter names.\n\
                         -i:  construct scale using specified root\n\
                              and intervals (in half-steps).\n\
                         -n:  construct scale using specified notes.\n"

#define  OP_HELP       "-h"
#define  OP_SHARPS     "-s"
#define  OP_POS_MARK   "-m"
#define  OP_INTERVALS  "-i"
#define  OP_NOTES      "-n"

#define  NUM_FRETS      20         /* numbered 0-19 */
#define  NUM_STRINGS     6         /* numbered 1-6 */
#define  MAX_NOTES      12

#define  FLATS           0
#define  SHARPS          1

#define  NECK_INDEX "\
    +       +       +       +       +          + +\
          +       +       +\n"

/* Basic macros */

#define  streq(s1, s2)  (strcmp(s1, s2) == 0)

#define  not_option(arg)  !(streq(arg, OP_HELP) ||\
                            streq(arg, OP_SHARPS) ||\
                            streq(arg, OP_POS_MARK) ||\
                            streq(arg, OP_INTERVALS) ||\
                            streq(arg, OP_NOTES))

/* function declarations */

char   *get_note();
short  note_index();
void   error();
void   print_fretbd();
void   print_note();

/* global variables */

static char *chrom[2][12] =
{ /* 1        2        3   4        5        6        7 */
   {"C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B"},
  /* 0   1    2   3    4   5   6    7   8    9   10   11 */
   {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"}
};

char  pos_mark_char = '\0';
unsigned short  note_type;
unsigned short  num_notes = 0;

/* ********************************************************************** */

main( argc, argv )
int   argc;
char  *argv[];
{
   char   *scale[MAX_NOTES];
   short  note_idx;
   unsigned short  a, i, interval;
   unsigned short  dflt_note_type = FLATS;

   for( i=0; i < MAX_NOTES; i++ )  /* initialize scale array */
      scale[i] = "-";

   /* Parse and verify command-line args.  Args are checked for
    * consistency with any associated option flag (i.e. if the -i option
    * is given, the following arg should be a valid note name, and
    * subsequent args should be valid intervals or a new option flag).
    */

   for( a=1; a < argc; a++ )
   {
      if( streq(argv[a], OP_HELP) )
         error( "", "" );          /* not really an error */

      else if( streq(argv[a], OP_SHARPS) )
         dflt_note_type = SHARPS;

      else if( streq(argv[a], OP_POS_MARK) )
      {
         if( (a+1 < argc) && not_option(argv[a+1]) )
            sscanf( argv[++a], "%c", &pos_mark_char );
         else
            error( "", "Specify one character for position mark." );
      }
      else if( streq(argv[a], OP_INTERVALS) )
      {
         i = 0;
         while( (a+1 < argc) && not_option(argv[a+1]) )
         {
            if( i == 0 )  /* first arg to -i option should be name of root */
            {
               /* convert case if needed */

               if( islower(*(argv[++a])) )
                  *(argv[a]) = toupper( *(argv[a]) );

               if( (note_idx = note_index(argv[a])) >= 0 )  /* valid note? */
               {
                  scale[i++] = chrom[note_type][note_idx];

                  /* if root is an accidental, the scale should be */
                  /* formed from the same note type, i.e. bs or #s */

                  switch( note_idx )
                  {
                  case 1: case 3: case 6: case 8: case 10:  /* accidentals */
                     dflt_note_type = note_type;
                  }
               }
               else
                  error( argv[a], "is not a valid note!" );
            }
            else      /* subsequent args to -i option should be intervals */
            {
               if( sscanf( argv[++a], "%hu", &interval ) == 1 )
               {
                  /* determine new note_idx from last note_idx */
                  note_idx = note_index(scale[i-1]) + (short)interval;

                  while( note_idx > 11 )     /* correct any overflow */
                     note_idx =- 12;

                  scale[i++] = chrom[dflt_note_type][note_idx];
               }
               else
                  error( argv[a], "is not a valid interval!" );
            }
         }
         num_notes = i;
      }
      else if( streq(argv[a], OP_NOTES) )
      {
         i = 0;
         while( (a+1 < argc) && not_option(argv[a+1]) )
         {
            /* convert case if needed */

            if( islower(*(argv[++a])) )
               *(argv[a]) = toupper( *(argv[a]) );

            if( note_index(argv[a]) >= 0 )   /* valid note? */
               scale[i++] = argv[a];
            else
               error( argv[a], "is not a valid note!" );
         }
         num_notes = i;
      }
   } /* end of argc loop */

   /* print scale */

   printf( "Scale =" );
   for( i=0; i < num_notes; i++ )
      printf( " %s", scale[i] );
   printf( " \n" );

   print_fretbd( scale );

   exit(0);
}

/* ********************************************************************** */

void  error( item, message )
char  *item, *message;
/* Print error message, help message, and exit. */
{
   fprintf( stderr," %s %s\n", item, message );
   fprintf( stderr, HELP_MESSAGE );
   exit(-1);
}


/* ********************************************************************** */

short  note_index( note )
char  *note;
/* Returns the chromatic scale index of note and sets note_type
 * if note is an accidental; returns -1 if note is not a member
 * of the chromatic scale.
 */
{
   short  i;

   for( i=0; i < 12; i++ )
   {
      note_type = FLATS;
      if( streq(note, chrom[note_type][i]) )
         return(i);
      note_type = SHARPS;
      if( streq(note, chrom[note_type][i]) )
         return(i);
   }
   return(-1);
}

/* ********************************************************************** */

void  print_fretbd( scale )
char  *scale[];
/* Print fretboard, labeling notes in scale[]. */
{
   char  *note;
   register unsigned short  string, fret_idx, fret;

   printf( NECK_INDEX );

   for( string=1; string <= NUM_STRINGS; string++ )
   {
      switch( string )
      {
      case 2:  fret_idx = 11; break;    /* B */
      case 3:  fret_idx = 7; break;     /* G */
      case 4:  fret_idx = 2; break;     /* D */
      case 5:  fret_idx = 9; break;     /* A */
      default: fret_idx = 4; break;     /* E */
      }

      for( fret=0; fret < NUM_FRETS; fret++ )
      {
         note = get_note( fret_idx, scale );
         print_note( note, fret );

         fret_idx++;
         while( fret_idx > 11 )
            fret_idx -= 12;
      }
      printf( "\n" );
   }
}

/* ********************************************************************** */

char  *get_note( fret_idx, scale )
unsigned short  fret_idx;
char  *scale[];
/* Returns note if chrom[][fret_idx] is a member of scale[];
 * returns "-" if chrom[][fret_idx] is not a member of scale[].
 */
{
   register unsigned short  i;

   for( i=0; i < num_notes; i++ )
   {
      if( streq( scale[i], chrom[FLATS][fret_idx] ) ||
          streq( scale[i], chrom[SHARPS][fret_idx] ) )
         return( scale[i] );
   }
   return( "-" );
}

/* ********************************************************************** */

void  print_note( note, fret )
char  *note;
unsigned short  fret;
/* Prints note or specified pos_mark_char, then proper fill chars. */
{
   if( streq( note, "-" ) || (pos_mark_char == '\0') )
   {
      printf( "%s", note );

      /* if note is a single-char string, print "-" */

      if( (fret > 0) && (*(note + 1) == '\0') )
         printf( "-" );
   }
   else
   {
      printf( "%c", pos_mark_char );
      if( fret > 0 )
         printf( "-" );
   }

   switch( fret )                  /* print fret indicators */
   {
   case 0:   printf( "||-" );  return;
   case 19:  printf( "|" );  return;
   default:  printf( "|-" );  return;
   }
}

/* ******************************* END ********************************** */
