[rec.music.makers] Guitarist's Helper

fcg@philabs.philips.com (Frank C. Guida) (01/10/90)

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 ********************************** */
______________________________________________________________________
Frank C. Guida                          Philips Laboratories
fcg@philabs.philips.com                 Autonomous Systems Dept.
(914)-945-6246                          Briarcliff Manor, NY  10510    

fcg@synapse (Frank C. Guida) (01/13/90)

Sorry folks,

I discovered a typo shortly after posting my fretbd program to the net.
I'd also like to thank the other readers who pointed this bug out.

The problem is the loop near line 170.  Its purpose being to bring the
note index into the correct range (0-11) when building a scale from an
interval pattern.

                  while( note_idx > 11 )     /* correct any overflow */
                     note_idx =- 12;

The "=-" should be changed to "-=".  Newer compilers will expand this
to "note_idx = - 12", resulting in a negative array subscript which
will cause various errors.

Stephen R. Tate <cs.duke.edu!srt@uunet> suggested a more elegant
solution to the problem:  replace the entire loop with the single line

                  note_idx %= 12;            /* correct any overflow */

A similar situation can be found in the function print_fretbd(), at
around line 176 (except that here I used the correct form of "-=").

Thanks again for your input.

- Yours truly

______________________________________________________________________
Frank C. Guida                          Philips Laboratories
fcg@philabs.philips.com                 Autonomous Systems Dept.
(914)-945-6246                          Briarcliff Manor, NY  10510    
______________________________________________________________________
Frank C. Guida                          Philips Laboratories
fcg@philabs.philips.com                 Autonomous Systems Dept.
(914)-945-6246                          Briarcliff Manor, NY  10510