💾 Archived View for blitter.com › OLGA › MUSIC › RESOURCES › CHART_UTILS › FINDCRD.C captured on 2022-06-12 at 05:34:52.
View Raw
More Information
-=-=-=-=-=-=-
- *******************************************************************************
-
- File: findcrd.c
- RCS: $Header: $
- Description: Finds the name of a guitar chord. The input is transformed to
- offsets (in frets) of the notes wrt. to the lowest string.
- The offsets are sorted.
- Next a jump table is created, this contains for each half note,
- the distance up to the next note found in the input pattern.
- The jump pattern is concatenated to itself. The patterns in
- the chord database are matched with the jump table. If the
- chord is not matched exactly, a set of alterations is tried
- to yield a resolution of the chord.
- The resolutions output are prefixed with a number indicating
- the "quality" of the resolution. Meant to be used as a sorting
- criteria.
- Bugs: Does only make ONE alteration to each chord in the database.
- E.g. x 5 x 9 7 x is reported as
- A6sus4(no 1)/D or
- E7sus2(no 5)/D
- and not as D(add9)(no 5).
- Several others, I am sure ;-)
- Please report them if you find them (bugs that is.)
- Author: Staal Vinterbo (staalv@idt.unit.no)
- Created: Mon Jun 5 13:36:08 1995
- Modified: Fri Jun 9 17:50:59 1995 (Staal Vinterbo) staal@gartimus.gartnet
- Language: C
- Package: N/A
- Status: EXPERIMENTAL
-
- (c) Copyright 1995, Staal Vinterbo, all rights reserved.
- I GIVE NO WARRANTY WHATSOEVER. YOU MAY DO WHAT EVER YOU LIKE WITH THIS
- CODE, EXCEPT INCORPORATE IT PARTIALLY OR WHOLLY IT IN A COMMERCIAL PRODUCT
- WITHOUT THE AUTHOR'S APPROVAL.
-
- *******************************************************************************
-
- Revisions:
-
- Fri Jun 9 15:26:50 1995 (Staal Vinterbo) staal@gartimus.gartnet
- Made sure max and min are undef'ed before defining them. Added bass note
- to named chords resolved to a different root than the lowest tone given
- in the input pattern. Added version string. Cleaned up some output code.
- *******************************************************************************
- /
char version[] = "FINDCHORD (findcrd.c) v.1.01, (c) SAV 1995";
#include <stdio.h>
#include <assert.h> /* assert, define NDEBUG to get rid of this */
#include <string.h> /* memset, memcpy, strcat */
#include <stdlib.h> /* atoi, in a pinch: replace with sprintf */
#ifdef DEBUG
# define DEB(s) s
# define NODEB(s)
# define PI(off,str,s,a,i) { \
int _c; \
fprintf(str, s); \
for(_c = 0; _c < i; _c++) \
fprintf(str, " %d",(a[_c] + off) % 12); \
fprintf(str, "\n"); \
}
#else
# define DEB(s)
# define NODEB(s) s
# define PI(off,str,s,a,i)
#endif
/* want to make sure max and min do what I want */
#ifdef min
#undef min
#endif
#define min(a, b) ((a) < (b) ? (a) : (b))
#ifdef max
#undef max
#endif
#define max(a, b) ((a) > (b) ? (a) : (b))
#define STRINGS 6 /* guitar */
#define MAXDIST 2 /* default max number of halfsteps to move note */
/*****************/
struct chord
{
int len; /* number of notes actually in this chord */
int pos[STRINGS]; /* the positions of the notes (normalized) */
char * name;
};
struct chord chords[] = /* the chord example database */
{
{3, {0,4,7,0,0,0},""},
{3, {0,3,7,0,0,0},"m"},
{4, {0,4,7,10,0,0},"7"},
{3, {0,3,6,0,0,0},"dim"},
{3, {0,4,8,0,0,0},"aug"},
{4, {0,3,6,8,0,0},"dim6"},
{4, {0,4,8,9,0,0},"aug6"},
{4, {0,3,7,11,0,0},"minmaj7"},
{4, {0,4,7,11,0,0},"maj7"},
{4, {0,3,7,10,0,0},"m7"},
{4, {0,3,6,9,0,0},"dim7"},
{4, {0,3,6,10,0,0},"m7b5"},
{4, {0,4,7,9,0,0},"6"},
{4, {0,3,7,9,0,0},"m6"},
{3, {0,2,7,0,0,0},"sus2"},
{3, {0,5,7,0,0,0},"sus4"},
{4, {0,2,7,9,0,0},"6sus2"},
{4, {0,5,7,9,0,0},"6sus4"},
{4, {0,2,7,10,0,0},"7sus2"},
{4, {0,5,7,10,0,0},"7sus4"},
{4, {0,2,7,11,0,0},"maj7sus2"},
{4, {0,5,7,11,0,0},"maj7sus4"},
{4, {0,4,8,11,0,0},"augb7"}, /* standard name? */
{2, {0,7,0,0,0,0}, "5"},
{5, {0,2,4,7,10,0},"9"},
{5, {0,2,3,7,10,0},"m9"},
{5, {0,2,4,7,11,0},"maj9"},
{5, {0,2,3,6,9,0},"dim9"},
{6, {0,2,4,5,7,10},"11"},
{6, {0,2,3,5,7,10},"m11"},
{6, {0,2,4,5,7,11},"maj11"},
{6, {0,2,3,5,6,9},"dim11"},
{6, {0,2,4,7,9,10},"13"},
{6, {0,2,3,7,9,10},"m13"},
{6, {0,2,4,7,9,11},"maj13"},
{0, {0,0,0,0,0,0}, "noname"} /* end marker */
};
char * notenames[12] = {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"};
char * sharpnotenames[12] =
{"1","#1","2","#2","3","4","#4","5","#5","6","#6","7"};
char * bnotenames[12] =
{"1","b2","2","b3","3","4","b5","5","b6","6","b7","7"};
char * addnotenames[12] =
{"1","#1","9","#9","3","11","#11","5","#5","13","#13","7"};
char * addbnotenames[12] =
{"1","b9","9","b3","3","11","b5","5","b13","13","b7","7"};
int lowstring = 4; /* index in notename array (4 == E) */
int stringdiff[STRINGS] = {5, 5, 5, 4, 5, 0}; /* number of halftones */
/* between the strings */
int jumps[24]; /* foreach halftone, the distance to the next note */
/* in the pattern, duplicated */
int pat_len = 0; /* the length of the pattern */
int minusones[STRINGS] = {-1, -1, -1, -1, -1, -1}; /* STRINGS times -1 */
int frets[STRINGS] = {-1, -1, -1, -1, -1, -1}; /* the input fret values, */
/* -1 is unused */
int offsets[STRINGS] = {-1, -1, -1, -1, -1, -1};
int offmap[24];
int maxdist = MAXDIST; /* the distance cutoff value */
char * pname;
int bass_note = 0; /* the lowest note */
int matched = 0; /* count matched patterns */
/* protos */
void prepare_jumps();
int match(struct chord *); /* returns non-zero if something was matched */
void main(int, char **);
void usage_exit();
void output(int missing, int nohit,int relroot, struct chord * c, int bass);
/* dirty details below */
void prepare_jumps(char ** args)
{
int i = 0, j = 0, d = 0, t = 0, used[12];
pat_len = STRINGS;
memset(used, 0, sizeof(used));
memcpy(frets, minusones, sizeof(frets));
/* calculate fretvalues of all notes on the lowest string (offsets) */
t = 10000; /* a high value */
for(i = 0; i < STRINGS; i++)
{
assert(args + i);
if(args[i][0] == 'x')
pat_len--; /* unused */
else
{
int m;
frets[i] = atoi(args[i]);
if(frets[i] + d < t) /* find the lowest note */
{
bass_note = j;
t = frets[i] + d;
}
m = (frets[i] + d) % 12;
if(!(used[m]++)) /* dont need same note more then once */
offsets[j++] = m;
}
d += stringdiff[i]; /* next string */
}
assert(j <= pat_len);
pat_len = j;
/* find the bass note */
bass_note = (offsets[bass_note] + lowstring) % 12;
/* sort offsets */
for(i = 0; i < pat_len; i++)
{
d = i;
for(j = i + 1; j < pat_len; j++)
if(offsets[j] < offsets[d])
d = j;
if(d > i)
{
t = offsets[i];
offsets[i] = offsets[d];
offsets[d] = t;
}
}
/* debug stuff */
PI(0,stderr,"offsets: ", offsets, pat_len);
PI((12-offsets[0]),stderr,"offsets norm: ", offsets, pat_len);
/* calculate, for each halfnote in the octave, the distance to the */
/* next note in the input pattern (i.e. input offsets 0 4 7 (a major */
/* chord) the jumptable becomes 0 3 2 1 0 2 1 0 4 3 2 1. Note that the */
/* table is contructed such that the first value is a 0.) A 0 value */
/* denotes that this note is in the input pattern. The offset map, */
/* offmap, is the mapping from the 0 values to the corresponding */
/* index in the offset array. (For the jumptable above this is */
/* 0 - - - 1 - - 2 - - - - , a - may be any number as it is not used) */
t = 0;
for(i = 0; i < pat_len - 1; i++)
{
jumps[t] = 0;
offmap[t] = i;
d = offsets[i+1] - offsets[i];
for(j = 1; j <= d; j++)
jumps[++t] = d - j;
}
jumps[t] = 0;
offmap[t] = i;
for(i = t + 1; i < 12; i ++)
jumps[i] = 12 - i;
PI(0,stderr,"jumps: ", jumps, 12);
/* duplicate to upper 12 so that we can use it to match the chord */
/* patterns in the chords array with any positive displacement not */
/* larger than 12 */
memcpy(jumps + 12, jumps, 12 * sizeof(int));
memcpy(offmap + 12, offmap, 12 * sizeof(int));
}
int match(struct chord * c)
{
int mind = 0, /* the minimal displacement that yields at least one match */
jump = 0, /* the current total dispacement */
hits, /* number of matches found using the current displacement */
i, j;
int nohit = -1; /* the index of the note in the chord pattern not */
/* matched in the input pattern */
int root = 0; /* the fret value of the root note relative to the low */
/* string */
int delta = 0; /* the number of half notes to move a non-matched note */
/* to make it match */
int offshit[STRINGS]; /* used to record if this offset (input pattern) */
/* was matched */
matched = 0; /* count matched patterns */
if((c->len > pat_len + 1) /* chord has more then one tone too much */ ||
(pat_len - c->len > 1) /* more than one alteration */)
{
return matched; /* 0 */
}
while(jump + mind < 12) /* need only to match lower 12 of jumps */
{
jump += mind; /* jump ahead to next possible match */
mind = 13; /* bigger that possible */
hits = 0;
nohit = -1;
delta = 0;
memset(offshit, 0 , sizeof(offshit));
for(i = 0; i < max(pat_len, c->len); i++)
{
j = jumps[c->pos[i] + jump];
/* j now contains 0 or the number of halftones to the next note */
/* in the pattern */
if(!j) /* this was a match */
{
hits++;
offshit[offmap[c->pos[i] + jump]]++; /* record the hit */
continue;
}
/* else */
nohit = i; /* the index of the note not matched in the chord */
if(j < mind) /* find the least jump forward to the next position */
mind = j;/* that gives at least one match */
}
if(hits >= c->len - 1) /* filter out the positions that do not match */
/* in all positions but one */
{
int missing = -1; /* the offset of the note in the pattern */
/* that is not matched in the chord */
root = (jump + offsets[0]) % 12; /*calculate the root */
/* find (if it exists) the note in the pattern that is not */
/* matched by a note in the chord */
for(i = 0; i < pat_len; i++)
if(!offshit[i])
{
missing = (offsets[i] + 12 - root) % 12;
assert(missing >= 0);
break; /* there is only one of these, so we need */
/* not look further */
}
/* nohit contains the index in the c->pos array of the */
/* note in the chord not matched in the pattern */
/* the resolved chord is named by "unifying" missing and */
/* nohit. */
output(missing, nohit, root, c, bass_note);
}
}
return matched;
}
void output(int missing, int nohit,int relroot, struct chord * c, int bass)
{
char buf[BUFSIZ], format[BUFSIZ], cc = '#';
int delta = 0, i, val;
sprintf(buf, "%s%s", notenames[(relroot + lowstring) % 12], c->name);
if((val = (missing != -1) | ((nohit != -1) << 1)) != 0)
{
if(maxdist == 0) /* only exact matches */
return;
i = strlen(buf);
switch(val)
{
case 1: /* missing != -1 && nohit == -1 => add missing to chrd */
assert(missing != -1 && nohit == -1);
delta = 1;
sprintf(buf + i,"(add%s)",addnotenames[missing]);
break;
case 2: /* missing == -1 && nohit != -1 remove nohit from chord */
assert(missing == -1 && nohit != -1);
delta = 1;
sprintf(buf + i,"(no %s)",bnotenames[c->pos[nohit]]);
break;
case 3: /* missing != -1 && nohit != -1 */
assert(missing != -1 && nohit != -1);
if(pat_len != c->len)
return; /* we have too few notes in the chord */
delta = missing - c->pos[nohit];
delta = delta < 0 ? cc = 'b', -delta : delta;
assert(delta < 12);
if(delta > maxdist)
return;
memset(format, cc, delta);
format[delta++] = 0;
sprintf(buf + i,"(%s%s)",format, bnotenames[c->pos[nohit]]);
}
}
if((relroot + lowstring) % 12 != bass)
{
strcat(buf, "/");
strcat(buf, notenames[bass]);
}
fprintf(stdout, "%d %s", delta, buf);
PI(jump,stdout,"\t", c->pos, c->len);
NODEB(fprintf(stdout, "\n"));
matched++;
}
void main(int argc, char ** argv)
{
char inbuf[BUFSIZ];
char the_args[STRINGS][80];
char ** cp = argv + 1;
int in = 0;
int matched = 0;
int interactive = 0;
struct chord * c = chords;
int i;
pname = argv[0];
if(argc == 2 + STRINGS)
{
maxdist = atoi(argv[1]);
argc--;
argv++;
}
if(argc == 2 || argc == 3)
{
if(!strcmp(argv[1], "-v"))
{
fprintf(stdout, "%s\n", version);
exit(0);
}
if(strcmp(argv[1], "-i"))
usage_exit();
if(argc == 3)
{
maxdist = atoi(argv[2]);
fprintf(stderr, "max distance set to %d\n", maxdist);
}
interactive = 1;
for(i = 0; i < STRINGS; i++)
cp[i] = the_args[i];
}
else
if(argc != 1 + STRINGS)
usage_exit();
while(!interactive || gets(inbuf))
{
if(interactive && sscanf(inbuf, "%s %s %s %s %s %s",cp[0],cp[1],
cp[2],cp[3],cp[4],cp[5]) != STRINGS)
{ /* change statement above if you change */
/* value of STRINGS */
fprintf(stderr, "malformed input\n");
continue;
}
in++;
prepare_jumps(cp);
i = 0;
while(c->len)
i += match(c++);
if(!i)
puts("sorry");
else
matched++;
c = chords;
if(!interactive)
break;
}
fprintf(stderr, "matched %d of %d\n", matched, in);
}
void usage_exit()
{
fprintf(stderr, "%s\nusage: %s [maxdistance] <values>\n"
"or %s -i [maxdistance]\n"
"or %s -v\n"
" <values> are the space-separated fret positions for each string,\n"
" 'x' for unused.\n"
" -i is for interactive input of <values>, one set pr. line.\n"
" -v prints the version string.\n"
" [maxdistance] is the maximal distance you want the altered\n"
" note to move. %d is default. Addition or subtraction of a note\n"
" counts as 1.\n"
" Each resolution is prefixed by a number that is\n"
" derived from this distance. Lower value means smaller\n"
" alteration to the chord from which the resolution was found.\n",
version,pname,pname,pname,maxdist);
exit(1);
}
/* that's all folks */