From: pmk@moliere.uucp (Peter Michael Klausler)
Subject: Fretboard / scale printing program
Organization: Lilydale DFL Refreshments Committee & Rhythm Section
Date: Fri, 10 Apr 1992 03:37:04 GMT

/* fretbd - Fretboard printing program for scales and chords
 *
 * Usage:
 *	fretbd [ tonic [ scale [ firstfret [ lastfret ] ] ] ]
 * or
 *	fretbd scales
 *
 * By default it prints 24-fret guitar fretboards in the standard tuning, but
 * if compiled with one of these options it will be configured for another
 * instrument or tuning:
 *
 *	-DSTICK		Chapman Stick standard tuning
 *	-DBARITONE	Chapman Stick "baritone" tuning
 *	-DDOUBLEBASS	Chapman Stick "double-bass" tuning
 *	-DBASS		"Precision" electric bass
 *	-DREADNOUGHT	Martin D-28 style acoustic (14 frets above the body)
 *	-DOPEN_D	Ditto, but in a common open D tuning
 *
 * Compile with -DHORIZ to print fretboards left-to-right rather than
 * top-to-bottom.
 *
 * Me, I build fretbd.c several times into different commands, like this:
 *
 *	cc -o stick -DSTICK -DHORIZ fretbd.c
 *	cc -o guitar -DREADNOUGHT fretbd.c
 *	cc -o bass -DBASS fretbd.c
 *
 * 2/4/91 - An execrable afternoon hack written to figure out the Stick.
 * 4/9/92 - Cleaned up a bit. Still needs more chords and tunings, as well
 *		as some sort of high-overhead point'n'drool interface.
 *
 * Permission is granted to academic institutions to use this C source
 * code as a bad example with which to frighten young persons.
 *
 * Peter Klausler (pmk%moliere@uunet.uu.net, pmk@cray.com).
 *
 */


#include <stdio.h>

extern long strtol ();


char *usage [] = {
	"usage:",
	"\tfretbd [ key [ scale [ fret [ last ] ] ] ]",
	"where",
	"\tkey\tis the tonic; Eb, E-, eb, e-, f--, D#, d#, and d+ all work",
	"\tscale\tis the name of a chord or mode, or an octal tone mask",
	"\tfret\tis the lowest fret to print",
	"\tlast\tis the highest fret to print",
	"or",
	"\tfretbd scales",
	"for a list of known scales",
	0
};


/* Identify the intervals of Western music within an octave, internally, as
 * follows:
 */
#define UNISON		04000
#define M_SECOND	02000
#define	SECOND		01000
#define M_THIRD		00400
#define THIRD		00200
#define FOURTH		00100
#define	TRITONE		00040
#define	FIFTH		00020
#define M_SIXTH		00010
#define SIXTH		00004
#define M_SEVENTH	00002
#define SEVENTH		00001

#define CHROMATIC	07777
#define IONIAN		(UNISON|SECOND|THIRD|FOURTH|FIFTH|SIXTH|SEVENTH)

/* Test interval to see if it's in a mask. This is a little too cute. */
#define inmask(i,m)	(!!((1 << (11-(i))) & (m)))

/* Transposition of modes. This is WAY too cute. */
#define transp(k,m)	(((m) << (12-(k))) & CHROMATIC | ((unsigned)(m) >> (k)))

/* Magic */
int basenote [] = { /*A*/ 9, 11, 0, 2, 4, 5, 7 /*G*/ };
char sharpnames [] = "CCDDEFFGGAAB";
char flatnames [] =  "CDDEEFGGAABB";

/* Scales and chords */
struct scale {
	char *name;
	int mask;
} scale [] = {

	/* scales */
	"chromatic",	CHROMATIC,
	"all",		CHROMATIC,
	"only",		UNISON,
	"whole",	(UNISON|SECOND|THIRD|TRITONE|M_SIXTH|M_SEVENTH),
	"ionian",	IONIAN,
	"dorian",	transp (10, IONIAN),
	"phrygian",	transp (8, IONIAN),
	"lydian",	transp (7, IONIAN),
	"mixolydian",	transp (5, IONIAN),
	"aeolian",	transp (3, IONIAN),
	"locrian",	transp (1, IONIAN),
	"pentatonic",	(UNISON|SECOND|THIRD|FIFTH|SIXTH),
	"pent-minor",	(UNISON|M_THIRD|FOURTH|FIFTH|M_SEVENTH),
	"blues",	(UNISON|M_THIRD|FOURTH|TRITONE|FIFTH|M_SEVENTH),

	/* chords */
	"major",	(UNISON|THIRD|FIFTH),
	"minor",	(UNISON|M_THIRD|FIFTH),
	"augmented",	(UNISON|THIRD|M_SIXTH),
	"diminished",	(UNISON|M_THIRD|TRITONE),	/* ambiguous name */
	"7",		(UNISON|THIRD|FIFTH|M_SEVENTH),
	"maj7",		(UNISON|THIRD|FIFTH|SEVENTH),
	"min7",		(UNISON|M_THIRD|FIFTH|M_SEVENTH),
	"dim7",		(UNISON|M_THIRD|TRITONE|SIXTH),
	"suspended",	(UNISON|FOURTH|FIFTH),
	"sus4",		(UNISON|FOURTH|FIFTH),
	"add9",		(UNISON|THIRD|FIFTH|SECOND),
	"9",		(UNISON|THIRD|FIFTH|M_SEVENTH|SECOND),

	/* More? Put them here */

	/* terminating sentinel */
	0,		0
};

/* Identify the notes in the range of a Stick or bass by calling the Db on the
 * lowest string, lowest fret, note 1 of octave 1; this makes middle C note 0 of
 * octave 4.
 */
#define notenum(n,o)	(12*((o)-1)+(n))
#define octave(nn)	((nn)/12+1)
#define octvnote(nn)	((nn)%12)


/* Instruments and tunings */

/* Chapman Stick baritone tuning */
#ifdef BARITONE
#define FRETS		25
#define STRINGS		10
#define	DOT(s,f)	((s) == 6 && (f) % 5 == 2)
#define FIRSTFRET	1
int opentuning [STRINGS] = {
	notenum (9, 3),		/* string 1: A, down a fourth */
	notenum (4, 3),		/* string 2: E, down a fourth */
	notenum (11, 2),	/* string 3: B, down a fourth */
	notenum (6, 2),		/* string 4: F#, down a fourth */
	notenum (1, 2),		/* string 5: Db, down a fourth */
	notenum (2, 1),		/* string 6: D, three octaves below middle C */
	notenum (9, 1),		/* string 7: A, up a fifth */
	notenum (4, 2),		/* string 8: E, up a fifth */
	notenum (11, 2),	/* string 9: B, up a fifth */
	notenum (6, 3),		/* string 10: F#, up a fifth */
};
#endif

/* Chapman Stick double-bass tuning */
#ifdef DOUBLEBASS
#define FRETS		25
#define STRINGS		10
#define	DOT(s,f)	((s) == 6 && (f) % 5 == 2)
#define FIRSTFRET	1
int opentuning [STRINGS] = {
	notenum (5, 3),		/* string 1: F below middle C */
	notenum (0, 3),		/* string 2: C, down a fourth */
	notenum (7, 2),		/* string 3: G, down a fourth */
	notenum (2, 2),		/* string 4: D, down a fourth */
	notenum (9, 1),		/* string 5: A, down a fourth */
	notenum (3, 1),		/* string 6: Eb, three octaves below middle C */
	notenum (10, 1),	/* string 7: Bb, up a fifth */
	notenum (5, 2),		/* string 8: F, up a fifth */
	notenum (0, 3),		/* string 9: C, up a fifth */
	notenum (7, 3),		/* string 10: G, up a fifth */
};
#endif

/* standard Chapman Stick tuning */
#ifdef STICK
#define FRETS		25
#define STRINGS		10
#define	DOT(s,f)	((s) == 6 && (f) % 5 == 2)
#define FIRSTFRET	1
int opentuning [STRINGS] = {
	notenum (2, 4),		/* string 1: D above middle C */
	notenum (9, 3),		/* string 2: A, down a fourth */
	notenum (4, 3),		/* string 3: E, down a fourth */
	notenum (11, 2),	/* string 4: B, down a fourth */
	notenum (6, 2),		/* string 5: F#, down a fourth */
	notenum (0, 1),		/* string 6: C, three octaves below middle C */
	notenum (7, 1),		/* string 7: G, up a fifth */
	notenum (2, 2),		/* string 8: D, up a fifth */
	notenum (9, 2),		/* string 9: A, up a fifth */
	notenum (4, 3),		/* string 10: E, up a fifth */
};
#endif

/* Fender bass */
#ifdef BASS
#define FRETS		24
#define STRINGS		4
#define	DOT(s,f)	0
#define FIRSTFRET	0
int opentuning [STRINGS] = {
	notenum (7, 2),		/* string 1: G (second one below middle C) */
	notenum (2, 2),		/* string 2: D, down a fourth */
	notenum (9, 1),		/* string 3: A, down a fourth */
	notenum (4, 1),		/* string 4: E, down a fourth */
};
#endif

/* 14-fret guitar, standard tuning */
#ifdef READNOUGHT
#define FRETS		14
#define STRINGS		6
#define	DOT(s,f)	(((s) == 4 && \
			  ((f) == 5 || (f) == 9 || (f) == 15 || (f) == 17)) || \
			 (((f) == 7 || (f) == 12) && \
			  ((s) == 2 || (s) == 6)))
#define FIRSTFRET	0
int opentuning [STRINGS] = {
	notenum (4, 4),		/* string 1: E above middle C */
	notenum (11, 3),	/* string 2: B, down a fourth */
	notenum (7, 3),		/* string 3: G, down a major third */
	notenum (2, 3),		/* string 4: D, down a fourth */
	notenum (9, 2),		/* string 5: A, down a fourth */
	notenum (4, 2),		/* string 6: E, down a fourth */
};
#endif

/* 14-fret guitar, open D tuning */
#ifdef OPEN_D
#define FRETS		14
#define STRINGS		6
#define	DOT(s,f)	(((s) == 4 && \
			  ((f) == 5 || (f) == 9 || (f) == 15 || (f) == 17)) || \
			 (((f) == 7 || (f) == 12) && \
			  ((s) == 2 || (s) == 6)))
#define FIRSTFRET	0
int opentuning [STRINGS] = {
	notenum (2, 4),		/* string 1: D above middle C */
	notenum (9, 3),		/* string 2: A, down a fourth */
	notenum (6, 3),		/* string 3: F#, down a minor third */
	notenum (2, 3),		/* string 4: D, down a major third */
	notenum (9, 2),		/* string 5: A, down a fourth */
	notenum (2, 2),		/* string 6: D, down a fifth */
};
#endif

/* 24-fret guitar, standard tuning; default case - must be last */
#ifndef FRETS
#define FRETS		24
#define STRINGS		6
#define	DOT(s,f)	(((s) == 4 && \
			  ((f) == 5 || (f) == 9 || (f) == 15 || (f) == 17)) || \
			 (((f) == 7 || (f) == 12) && \
			  ((s) == 2 || (s) == 6)))
#define FIRSTFRET	0
int opentuning [STRINGS] = {
	notenum (4, 4),		/* string 1: E above middle C */
	notenum (11, 3),	/* string 2: B, down a fourth */
	notenum (7, 3),		/* string 3: G, down a major third */
	notenum (2, 3),		/* string 4: D, down a fourth */
	notenum (9, 2),		/* string 5: A, down a fourth */
	notenum (4, 2),		/* string 6: E, down a fourth */
};
#endif


/* What note is at a given string/fret position? */
#define fretnote(s,f)	(opentuning [(s)-1] + (f))

/* Assigned names of notes - the output of namenotes () */
char octv_note [12][8];

/* Decide what to call each note, according to common conventions.
 * Usually picks the wrong names for anything slightly unusual.
 */
namenotes (tonic, letter, offset, mask)
	int tonic, letter, offset, mask;
{

	int i, j, uses [7], sway, note;
	char *basenames, *p;

	if (offset > 0) {

		/* If the tonic name used sharps, use sharps */
		basenames = sharpnames;
		offset -= (letter != 'B' && letter != 'E');

	} else if (offset < 0) {

		/* If the tonic name used flats, use flats */
		basenames = flatnames;
		offset += (letter != 'C' && letter != 'F');

	} else {

		/* No sharps or flats in the tonic name; try to pick a
		 * good convention for the supplied absolute mask.
		 */
		sway = 0;
		for (i = 0; i < 7; i++)
			uses [i] = 0;
		for (i = 0; i < 12; i++)
			if (mask & (04000 >> i))
				sway -= !!uses [sharpnames [i] - 'A']++;
		for (i = 0; i < 7; i++)
			uses [i] = 0;
		for (i = 0; i < 12; i++)
			if (mask & (04000 >> i))
				sway += !!uses [flatnames [i] - 'A']++;
		if (sway > 0)
			basenames = sharpnames;
		else if (sway < 0)
			basenames = flatnames;
		else if (letter == 'C' || letter == 'F')
			basenames = flatnames;
		else
			basenames = sharpnames;
	}

	for (i = 0; i < 12; i++) {
		p = octv_note [i];
		note = basenames [i] - offset;
		if (note < 'A')
			note += 7;
		else if (note > 'G')
			note -= 7;
		*p = note;
		if (basenames == sharpnames) {
			j = i - basenote [note - 'A'];
			if (j < 0)
				j += 12;
			for (; j > 0; j--)
				*++p = '#';
		} else {
			j = basenote [note - 'A'] - i;
			if (j < 0)
				j += 12;
			for (; j > 0; j--)
				*++p = 'b';
		}
		*++p = '\0';
	}
}

/* Print the notes on a fret which are in a mask */
printfret (fret, mask)
	int fret;
	int mask;
{
	int s, note, onote;
	char buf [8];
	for (s = STRINGS; s; s--) {
		note = fretnote (s, fret);
		onote = octvnote (note);
		if (inmask (onote, mask)) {
			sprintf (buf, "%s%d", octv_note [onote], octave (note));
			printf (" %-4s", buf);
		} else
			printf (" |   ");
		putchar (DOT (s, fret) ? '*' : ' ');
	}
}

/* Print the notes on a string which are in a mask */
printstring (string, mask, minfret, maxfret)
	int string;
	int mask;
	int minfret;
	int maxfret;
{
	int f, note, onote;
	char buf [8];
	for (f = minfret; f <= maxfret; f++) {
		note = fretnote (string, f);
		onote = octvnote (note);
		if (inmask (onote, mask)) {
			sprintf (buf, "%s%d", octv_note [onote], octave (note));
			printf ("%-3s", buf);
		} else
			printf ("-- ");
	}
}

/* Print a formatted range of frets */
main (argc, argv)
	int argc;
	char **argv;
{
	int ch, i, j, any;
	int letter = 'C';
	int offset = 0;
	int tonic = 0;
	int mask = CHROMATIC;
	int minfret = FIRSTFRET;
	int maxfret = FRETS;
	char *c, **u;

	/* Skip argument 0 */
	argc--, argv++;

	if (argc && !strcmp (*argv, "scales")) {
		for (i = 0; scale [i].name; i++)
			printf ("%s\n", scale [i].name);
		exit (0);
	}

	/* Argument 1: tonic */
	if (argc) {
		c = *argv++;
		letter = *c++;
		if (letter >= 'a' && letter <= 'g')
			letter += 'A' - 'a';
		if (letter < 'A' || letter > 'G')
			goto error;
		while (ch = *c++)
			switch (ch) {
			case '-':
			case 'b':
				offset--;
				break;
			case '+':
			case '#':
				offset++;
				break;
			default:
				 goto error;
			}
		mask = IONIAN;
		argc--;
	}

	/* Argument 2: scale */
	if (argc) {
		c = *argv++;
		if (*c == '0')
			mask = CHROMATIC & strtol (c, (char **) 0, 0);
		else {
			j = strlen (c);
			any = 0;
			for (i = 0; scale [i].name; i++)
				if (!strncmp (c, scale [i].name, j))
					if (!scale [i].name [j]) {
						mask = scale [i].mask;
						any = 1;
						break;
					} else {
						mask = scale [i].mask;
						any++;
					}
			if (!any) {
				fprintf (stderr, "bad scale name: %s\n", c);
				goto error;
			} else if (any > 1) {
				fprintf (stderr, "ambiguous scale: %s\n", c);
				goto error;
			}
		}
		argc--;
	}

	/* Argument 3: first fret */
	if (argc) {
		minfret = atoi (*argv++);
		if (minfret < 1 || minfret > FRETS)
			goto error;
		maxfret = minfret;
		argc--;
	}

	/* Argument 4: last fret */
	if (argc) {
		maxfret = atoi (*argv++);
		if (maxfret < minfret || maxfret > FRETS)
			goto error;
		argc--;
	}

	if (argc)
		goto error;

	tonic = basenote [letter - 'A'] + offset;
	while (tonic < 0)
		tonic += 12;
	tonic %= 12;
	mask = transp (tonic, mask);
	namenotes (tonic, letter, offset, mask);

#ifdef HORIZ
	/* Print fret numbers */
	printf ("   ");
	for (i = minfret; i <= maxfret; i++)
		printf ("%-3d", i);
	putchar ('\n');

	/* Print strings */
	for (i = 1; i <= STRINGS; i++) {
		printf ("%-3d", i);
		printstring (i, mask, minfret, maxfret);
		putchar ('\n');
	}

	/* Print fret numbers again */
	printf ("   ");
	for (i = minfret; i <= maxfret; i++)
		printf ("%-3d", i);
	putchar ('\n');
#else
	/* Print string numbers */
	printf ("       ");
	for (i = STRINGS; i; i--)
		printf ("  %-2d  ", i);
	putchar ('\n');

	/* Print frets */
	for (i = minfret; i <= maxfret; i++) {
		printf ("%3d:    ", i);
		printfret (i, mask);
		printf (" :%d\n", i);
	}

	/* Print string numbers again */
	printf ("       ");
	for (i = STRINGS; i; i--)
		printf ("  %-2d  ", i);
	putchar ('\n');
#endif

	exit (0);

	/* Print usage */
error:	for (u = usage; *u; u++)
		fprintf (stderr, "%s\n", *u);
	exit (1);
}
-- 
Peter Klausler - pmk%moliere@uunet.uu.net, or uunet!moliere!pmk - 612-686-8152
Xref: sparky rec.music.makers:5596 rec.music.misc:14165