💾 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

-=-=-=-=-=-=-



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 */