/* $Id: tbl2wav.c,v 1.27 2012-11-15 19:59:08+09 shige Exp $
 * translation tool of table output of gnuplot to sound data
 * Shigeharu TAKENO <shige@iee.niit.ac.jp>
 * (original: 08/29 2005 tbl2snd.c in yomi)
 */
/* This requires libsndfile (http://www.mega-nerd.com/libsndfile/).
 *   gcc -Wall -o tbl2wav tbl2wav.c -lsndfile -lm
 */
/* On MS-Windows, you can compile by VC++ with libsndfile for MS-Windows:
 *   cl tbl2wav.c /D_USE_MATH_DEFINES /D_MSVC_ /I%libsnd%\include 
 *      /link /libpath:%libsnd%\lib libsndfile-1.lib
 *   (%libsnd% = libsndfile installed directory)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include <sndfile.h>

typedef struct {
    double x, y;
    /* double h, t; */
    char R;
} TBLdata;

#define Maxbuff 8192
#define Minhz 15.0
#define Maxhz 100000.0
#define Maxchannels 8
#define Minsmplrate 100
#define Maxsmplrate 200000
#define Maxdatanum 5000
#define Maxgraphs 20

#define Default_volume 0.7
#define Default_hm 261.6256
#define Default_hM 523.2511
#define Default_T 5.0

#define AU_SMPLRATE 8000
#define WAV_SMPLRATE 11025
#define AIFF_SMPLRATE 11025
#define RAW_SMPLRATE 11025

#define NULLsnd 0.0
#define BASEPITCH 440.0
#define STDOUTF "-"

enum { FORMAT_WAV = 0, FORMAT_AU, FORMAT_AIFF, FORMAT_RAW };
#define Default_FORMAT FORMAT_WAV

#define TBLalloc(n) ((TBLdata *)malloc(sizeof(TBLdata)*(n))) 

#define MOVE_TO_PARAM_HEAD(argc,argv) \
  if (*(++ *argv) == '\0') { if (-- argc <= 0) return -4; else argv ++; }


/* ##### outer variable ##### */
double xm, xM, ym, yM, hm, hM, T;
long int blocks;

/* ##### prototype ##### */
int main(int argc, char *argv[]);
double pergene1(double x, double a);
double pergene2(double x, double a);
double pergene3(double x, double a, double b);
double perf(double x, int type);
int putglisdata3(SNDFILE *outfile, SF_INFO *sfinfo, double vol, int type,
		 TBLdata *data[], int N[], int M, int mmode, int umode, 
		 int dmode, int gmode);
double xy2level(double t, int ch, TBLdata *data[], int N[], int M, int type, 
		int mode, int gmode);
double interpXY(double x, double x0, double x1, double y0, double y1);
int setheader(SF_INFO *sfinfo, int oform, int ch, char *lenstr);
void TBLcpy(TBLdata *new, TBLdata *org, int n);
int chdata(TBLdata *data, int mode);
int chdatan(TBLdata *data, int mode, int n);
int getdata2(FILE *infile, TBLdata *data[], int N[], int *M, int dmode);
int setMaxMin(TBLdata *data[], int N[], int M, int mode);
double str2hz(char *s);
int getarg(int argc, char **argv, int *oform, double *vol, int *type, 
	   int *mmode, int *umode, int *dmode, int *gmode, char **lenstr, 
	   FILE **infile, char **outfname);
void usage(char *comname, int ret);

/* ##### main routine ##### */
int main(int argc, char *argv[])
{
    FILE *infile;
    SNDFILE *outfile;
    SF_INFO sfinfo;
    double vol;
    int type, ch, mmode, umode, dmode, gmode, ret, format;
    TBLdata *data[Maxgraphs];
    int N[Maxgraphs], M;
    char *lenstr, *outfname;
    
    if ((ret = getarg(argc, argv, &format, &vol, &type, &mmode, &umode, 
		      &dmode, &gmode, &lenstr, &infile, &outfname)) <= 0) {
	usage(argv[0], ret); return -ret;
    }
#ifdef DEBUG
    fprintf(stderr, "DEBUG: Pass.1\n");
#endif
    if ((ret = getdata2(infile, data, N, &M, dmode)) < 0) {
	usage(argv[0], ret - 30); return -ret;
    }
#ifdef DEBUG
    fprintf(stderr, "DEBUG: Pass.2\n");
#endif
    if (M <= 0) {
	usage(argv[0], ret - 40); return 100;
    }
    ch = (mmode == 1) ? 1 : M;
    if ((ret = setheader(&sfinfo, format, ch, lenstr)) < 0) {
	usage(argv[0], ret - 50); return -ret;
    }
#ifdef DEBUG
    fprintf(stderr, "DEBUG: Pass.3\n");
#endif
    if (outfname == NULL || strcmp(outfname, STDOUTF) == 0) {
	
    }
    if ((outfile = sf_open(outfname, SFM_WRITE, &sfinfo)) == NULL) {
	fprintf(stderr, "File [%s] open error.\n", outfname);
	return -5;
    }
    if ((ret = putglisdata3(outfile, &sfinfo, vol, type, data, N, M, 
		       mmode, umode, dmode, gmode)) < 0) {
	usage(argv[0], ret - 60); return -ret;
    }
    sf_close(outfile);
    return 0;
}

/* ########## subroutine ########## */
/* ##### periodic generator ##### */
double pergene1(double x, double a)
{
    double y;

    y = (a*cos(2.0*M_PI*x) + 1.0)/(a + 1.0);
    return 2.0*y*y - 1.0;
}

/* square wave */
double pergene2(double x, double a)
{
    int j;

    j = x;
    return (x - j < a) ? -1.0 : 1.0;
}


/* 0<a<1, -1<b<1 
 * poligonal lines through (0, 0), (a, 1), (1, b)
 */
double pergene3(double x, double a, double b)
{
    int j;

    j = x;
    x -= j ;
    return (x < a) ? 2.0*x/a - 1.0 : 2.0*(1.0 - x)/(1.0 - a) + b;
}


/* ##### periodic function with values in [-1.0:1.0] ##### */
double perf(double x, int type)
{

    switch (type%8) {
    case 0: return sin(2.0*M_PI*x); /* sine wave */
    case 1: { int j = x; return 2.0*(x - j) - 1.0; } /* sawtooth wave */
    case 2: return pergene2(x, 0.5); /* square wave */
    case 3: return pergene1(x, 2.0);
    case 4: return pergene3(x, 0.5, -1.0);
    case 5: return pergene2(x, 0.25);
    case 6: return pergene1(x, 8.0);
    case 7: return pergene3(x, 0.3, 0);
    default: return 0.0;
    }
}

int putglisdata3(SNDFILE *outfile, SF_INFO *sfinfo, double vol, int type,
		 TBLdata *data[], int N[], int M, int mmode, int umode, 
		 int dmode, int gmode)
{
    long int j;
    int k;
    double z, z0;
#ifndef __MSC__
    double z1[M];
#else
    double *z1;
#endif
    int mode, ctype;

#ifdef __MSC__
    z1 = (double *)malloc(sizeof(double)*M);
#endif
    mode = 2 - dmode;
    for (j = 0; j < blocks; j ++) {
	ctype = type;
	if (mmode == 1) { /* mix */
	    z = 0.0;
	    for (k = 1; k <= M; k ++) {
		if ((z0 = xy2level((double)j/blocks, k, data, N, M, 
				   ctype, mode, gmode)) < -1.0) 
		  return z0;
		z += z0;
		if (umode == 1) ctype ++;
	    }
	    z1[0] = z*vol/M;
	    sf_write_double(outfile, z1, 1);
	}
	else { /* divide to channels */
	    for (k = 1; k <= M; k ++) { 
		if ((z = xy2level((double)j/blocks, k, data, N, M, 
				  ctype, mode, gmode)) < -1.0) 
		  return z - 10;
		z1[k - 1] = z*vol;
		if (umode == 1) ctype ++;
	    }
	    sf_writef_double(outfile, z1, 1);
	}
    }
    return 0;
}

/* mode == 1: check only 'i', mode == 0: check also 'o'
 */
/* return the level data
 * M = number of graphs
 * data[0] ~ data[M - 1]: pointer to data
 * data[j][0] ~ data[j][N[j] - 1]: each data
 * ch: graph index (1 <= ch <= M) 
 * m[j]: last used data index (-1: no more data exist)
 * nullf[j]: whether the last data is null sound or not
 * off[j]: offset 
 */
double xy2level(double t, int ch, TBLdata *data[], int N[], int M, int type, 
		int mode, int gmode)
{
    static int first = 1;
    static int *m, *nullf;
    static double *off;
    int j, k;
    double x, z, y1;

    if (first) {
	if ((m = (int *)malloc(sizeof(int)*M)) == NULL
	    ||(nullf = (int *)malloc(sizeof(int)*M)) == NULL
	    ||(off = (double *)malloc(sizeof(double)*M)) == NULL) 
	  return -10.0;
	for (j = 0; j < M; j ++) { 
	    off[j] = 0.0;
	    nullf[j] = 1;
	    for (k = 0; k < N[j]; k ++) 
	      if (chdata(&(data[j][k]), mode)) { m[j] = k; break; }
	    if (k >= N[j]) m[j] = -1;
	}
	first = 0;
    }
    if (m[ch - 1] < 0) return NULLsnd; /* Null sound */
    x = xm + (xM - xm)*t;
    if (nullf[ch - 1] && x  <= data[ch - 1][m[ch - 1]].x) 
      return NULLsnd; /* Null sound */
    if (x > data[ch - 1][m[ch - 1]].x) {
	for (k = m[ch - 1]; k < N[ch - 1]; k ++) {
	    if (chdata(&(data[ch - 1][k]), mode)) { /* valid data */
		if (nullf[ch - 1]) {
		    if (x <= data[ch - 1][k].x) {
			m[ch - 1] = k; 
			return NULLsnd; /* Null sound */
		    }
		    else
			nullf[ch - 1] = 0;
		}
		else if (x <= data[ch - 1][k].x) {
		    m[ch - 1] = k;
		    break;
		}
		else {
		    z = interpXY(data[ch - 1][k].x, 
				 data[ch - 1][k - 1].x, data[ch - 1][k].x, 
				 data[ch - 1][k - 1].y, data[ch - 1][k].y);
		    if (z < 0.0) return -12.0;
		    else off[ch - 1] += z;
		}
	    }
	    else {
		off[ch - 1] = 0.0;
		nullf[ch - 1] = 1; 
	    }
	}
	if (k >= N[ch - 1]) {
	    m[ch - 1] = -1; return NULLsnd; /* Null sound */
						}
    }
    k = m[ch - 1];
    if (gmode == 1) y1 = data[ch - 1][k].y;
    else y1 = data[ch - 1][k - 1].y;
    if ((z = interpXY(x, data[ch - 1][k - 1].x, data[ch - 1][k].x, 
		      data[ch - 1][k - 1].y, y1)) < 0.0)
    return -13.0;
    return perf(z + off[ch - 1], type);
}

/* return the interpolated frequency data between (x0, y0) with (x1, y1) */
double interpXY(double x, double x0, double x1, double y0, double y1)
{
    double hr, H, z;
    
    if (x1 <= x0 || x0 < xm || x1 < xm || x0 > xM || x1 > xM) return -2.0;
    if (y0 < ym || y1 < ym || y0 > yM || y1 > yM) return -3.0;
    hr = hM/hm;
    H = hm*pow(hr, (y0 - ym)/(yM - ym));
    if (y0 == y1) return H*T*(x - x0)/(xM - xm);
    z = H*T*(x1 - x0)*(yM - ym)/((y1 - y0)*(xM - xm));
    return z*(pow(hr, (y1 - y0)*(x - x0)/((yM - ym)*(x1 - x0)))-1.0)/log(hr);
}

int setheader(SF_INFO *sfinfo, int oform, int ch, char *lenstr)
{
    double len;
    char buff[Maxbuff];
    int j;

    memset(sfinfo, 0, sizeof(sfinfo));
    switch (oform) {
    case FORMAT_AU:
	if (ch != 1) return -1;
	sfinfo->format = SF_FORMAT_AU | SF_FORMAT_ULAW;
	sfinfo->samplerate = AU_SMPLRATE;
	sfinfo->channels = ch;
	break;
    case FORMAT_WAV:
	sfinfo->format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
	sfinfo->samplerate = WAV_SMPLRATE;
	sfinfo->channels = ch;
	break;
    case FORMAT_AIFF:
	sfinfo->format = SF_FORMAT_AIFF | SF_FORMAT_PCM_16;
	sfinfo->samplerate = AIFF_SMPLRATE;
	sfinfo->channels = ch;
	break;
    case FORMAT_RAW:
	sfinfo->format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
	sfinfo->samplerate = RAW_SMPLRATE;
	sfinfo->channels = ch;
	break;
    default: return -1;
    }
#ifdef DEBUG
    fprintf(stderr, "DEBUG: (format, smpl, channel) = (%d, %d, %d)\n",
	    sfinfo->format, sfinfo->samplerate, sfinfo->channels);
#endif
    /* set blocks and T (from samplerate and lenstr) */
    if (lenstr == NULL ||(j = strlen(lenstr)) <= 0)
      blocks = Default_T*sfinfo->samplerate;
    else if (j >= Maxbuff) return -2;
    else {
	strcpy(buff, lenstr);
	if (buff[j - 1] == 'b') { /* block */
	    buff[j - 1] = '0';
	    if ((blocks = atoi(lenstr)) <= 0) return -2;
	}
	else {
	    if ((len = atof(lenstr)) <= 0.0) return -3;
	    blocks = len*sfinfo->samplerate/1000;
	}
    }
    T = (double)blocks/sfinfo->samplerate;
#ifdef DEBUG
    fprintf(stderr, "DEBUG: (blocks, T) = (%ld, %f)\n", blocks, T);
#endif
    return 0;
}

void TBLcpy(TBLdata *new, TBLdata *org, int n)
{
    int j;

    for (j = 1; j < n; j ++, new ++, org ++) *new = *org;
    *new = *org;
}

/* mode == 1: check only 'i', mode == 0: check also 'o'
 */
int chdata(TBLdata *data, int mode)
{
    if (mode == 0 && data->R != 'u') return 1;
    if (mode == 1 && data->R == 'i') return 2;
    return 0;
}

/* mode == 1: check only 'i', mode == 0: check also 'o'
 */
int chdatan(TBLdata *data, int mode, int n)
{
    int j;

    for (j = 1; j <= n; j ++) if (chdata(&data[j - 1], mode)) return 1;
    return 0;
}

int getdata2(FILE *infile, TBLdata *data[], int N[], int *M, int dmode)
{
    TBLdata databuff[Maxdatanum];
    char strbuff[Maxbuff];
    int len, ret, n, mode;

    *M = 0;
    n = 0;
    mode = 2 - dmode;
    while (fgets(strbuff, Maxbuff, infile) != NULL) {
	if ((len = strlen(strbuff)) >= Maxbuff - 2) return -1;
	if (strbuff[0] == '\n') { /* end of data */ 
	    if (n > 0) {
		if (chdatan(databuff, mode, n)) { /* exists valid data */
		    if (++ (*M) > Maxgraphs) return -4;
		    if ((data[*M - 1] = TBLalloc(n)) == NULL) return -5;
		    TBLcpy(data[*M - 1], databuff, n);
		    N[*M - 1] = n;
		}
		n = 0;
	    }
	}
	else if (strbuff[0] != '#') { /* each data */
	    if (++ n > Maxdatanum) return -2;
	    if (sscanf(strbuff, "%lf %lf %c", &(databuff[n - 1].x), 
		      &(databuff[n - 1].y), &(databuff[n - 1].R)) != 3) 
	      return -3;
	}
    }
    if ((ret = setMaxMin(data, N, *M, mode)) > 0) {
	if (ret%2) return -10;
	if (ret/2) {
	    if (ym == 0.0) { ym = -1.0; yM = 1.0; }
	    else if (ym > 0.0) { ym *=  0.5; yM *= 1.5; }
	    else { ym *= 1.5; yM *= 0.5; }
	    return 1;
	}
	else return -11;
    } 
    return 0;
}

/* mode == 1: check only 'i', mode == 0: check also 'o'
 * return value:
 *   xm == xM ==> + 1
 *   ym == yM ==> + 2
 */
int setMaxMin(TBLdata *data[], int N[], int M, int mode)
{
    int n, m, ret;
    double X, Y;

    for (m = 1; m <= M; m ++) {
	for (n = 1; n <= N[m - 1]; n ++) 
	  if (chdata(&data[m - 1][n - 1], mode)) break;
	if (n <= N[m - 1]) break;
    }
    if (m > M || n > N[m - 1]) return -1;
    xm = xM = data[m - 1][n - 1].x;
    ym = yM = data[m - 1][n - 1].y;

    for (; m <= M; m ++) {
	for (; n <= N[m - 1]; n ++) 
	  if (chdata(&data[m - 1][n - 1], mode)) {
	      if ((X = data[m - 1][n - 1].x) < xm) xm = X;
	      else if (X > xM) xM = X;
	      if ((Y = data[m - 1][n - 1].y) < ym) ym = Y;
	      else if (Y > yM) yM = Y;
	  }
	n = 1;
    }
    ret = 0;
    if (xm == xM) ret += 1;
    if (ym == yM) ret += 2;
    return ret;
}

/* return the frequency value the string means
 */
double str2hz(char *s)
{
    char c1, c2;
    int tr[] = {9, 11, 0, 2, 4, 5, 7}, d, oct;
    double x;

    if (*s  ==  '-' ) return -1.0;
    if (isdigit(*s)) return atof(s);
    if ((c1 = *s) < 'A' || c1 > 'G') return -2.0;
    if (*(++ s) == '\0') { oct = 0; c2 = 0; }
    else {
	if ((c2 = *s) == '#' || c2 == 'b') s ++;
	else c2 = 0;
	if (*s == '\0') oct = 0;
	else oct = atoi(s);
    }

    d = tr[c1 - 'A'];
    if (c2 == '#') {
	if (++ d >= 12) { oct ++; d -= 12; }
    }
    else if (c2 == 'b') {
	if (-- d < 0) { oct --; d += 12; }
    }
    x = BASEPITCH*pow(2.0, (d - 9)/12.0);
    if (oct < 0) for (; oct < 0; oct ++) x /= 2.0;
    else if (oct > 0) for (; oct > 0; oct --) x *= 2.0;
    if (x < Minhz || x > Maxhz) return -3.0;
    else return x;
}

int getarg(int argc, char **argv, int *oform, double *vol, int *type, 
	   int *mmode, int *umode, int *dmode, int *gmode, char **lenstr, 
	   FILE **infile, char **outfname)
{
    char c;

    *outfname = NULL;
    *vol = Default_volume;
    *type = 0;
    *oform = Default_FORMAT;
    hm = Default_hm; hM = Default_hM;
    *lenstr = NULL;
    *mmode = 1; *umode = 1; *dmode = 1, *gmode = 1;

    while (-- argc > 0) {
	if (**(++ argv) == '-') {
	    switch (*(++ *argv)) {
	    case 'H':
	    case '?': return 0;
	    case 'v':
		MOVE_TO_PARAM_HEAD(argc,argv)
		if ((*vol = atof(*argv)) < -1.0 || *vol > 1.0) {
		    fprintf(stderr, "Not correct v value (%s).\n", *argv);
		    return -12;
		}
		break;
	    case 't':
		MOVE_TO_PARAM_HEAD(argc,argv)
		if ((*type = atoi(*argv)) < 0) {
		    fprintf(stderr, "Not correct t value (%s).\n", *argv);
		    return -13;
		}
		break;
	    case 'a':
	    case 'w':
	    case 'r':
		if (!strcmp(*argv, "au")) *oform = FORMAT_AU;
		else if (!strcmp(*argv, "wav")) *oform = FORMAT_WAV;
		else if (!strcmp(*argv, "aiff")) *oform = FORMAT_AIFF;
		else if (!strcmp(*argv, "raw")) *oform = FORMAT_RAW;
		else {
		    fprintf(stderr, "Unknown option (%s)\n", *argv);
		    return -14;
		}
		break;
	    case '\0': break;
	    case 'l':
		MOVE_TO_PARAM_HEAD(argc,argv)
		*lenstr = *argv;
		break;
	    case 'h':
		if (*(++ *argv) != 'm' && **argv != 'M') return 0;
		c = **argv;
		MOVE_TO_PARAM_HEAD(argc,argv)
		if ((c  ==  'm' && (hm = str2hz(*argv)) < Minhz)
		   ||(c  ==  'M' && (hM = str2hz(*argv)) > Maxhz)) {
		    fprintf(stderr, "Not correct h%c value (%s).\n", c, *argv);
		    return -15;
		}
		break;
	    case 'm':
		MOVE_TO_PARAM_HEAD(argc,argv)
		if ((*mmode = atoi(*argv)) != 1 && *mmode != 2) {
		    fprintf(stderr, "Not correct mmode value (%s).\n", *argv);
		    return -16;
		}
		break;
	    case 'u':
		MOVE_TO_PARAM_HEAD(argc,argv)
		if ((*umode = atoi(*argv)) != 1 && *umode != 2) {
		    fprintf(stderr, "Not correct umode value (%s).\n", *argv);
		    return -17;
		}
		break;
	    case 'd':
		MOVE_TO_PARAM_HEAD(argc,argv)
		if ((*dmode = atoi(*argv)) != 1 && *dmode != 2) {
		    fprintf(stderr, "Not correct dmode value (%s).\n", *argv);
		    return -18;
		}
		break;
	    case 'g':
		MOVE_TO_PARAM_HEAD(argc,argv)
		if ((*gmode = atoi(*argv)) != 1 && *gmode != 2) {
		    fprintf(stderr, "Not correct gmode value (%s).\n", *argv);
		    return -19;
		}
		break;
	    default: return -6;
	    }
	    if (**argv == '\0') { -- *argv; break; }
	}
	else break;
    }

    if (*mmode == 2 && *oform == FORMAT_AU) {
	fprintf(stderr, "-au can't use with -mmode=2.\n");
	return -8;
    }
    if (hm >= hM || hm < Minhz || hM > Maxhz) {
	fprintf(stderr, "Not correct (hm, hM) value (%f, %f)\n", hm, hM);
	return -8;
    }
    if (argc != 2) {
	return -9;
    }
    if (**argv  ==  '-') *infile = stdin;
    else if ((*infile = fopen(*argv, "r")) == NULL) return -10;
    *outfname = *(++ argv);
    return 1;
}

void usage(char *comname, int ret)
{
    char *topline = "Usage: %s ([option]) [infile] [outfile]\n"; 
    char *mess[] = {
	"  [option]:", 
	"    -au/wav/aiff/raw: sound data format (default: WAV)", 
	"    -v[n]: volume (-1.0 ~ 1.0; default: 0.7)", 
	"    -t[n]: start wave pattern number (default: 0)",
	"    -l[n]: sound length (ms or blocks ([n]b); default: 5000 (ms))",
	"    -hm[n]: lowest tone (freq. or name; default: C0)", 
	"    -hM[n]: highest tone (freq. or name; default: C1)",
	"    -m[n]: mixing (1: mixing, 2: divide to channels; default: 1)", 
	"    -u[n]: using wave patterns (1: use, 2: unuse; default: 1)",
	"    -d[n]: using marked data (1: only 'i', 2: also 'o'; default: 1)", 
	"    -g[n]: using glissando (1: use, 2: unuse; default: 1)",
	" ", 
	" [0] This translates the gnuplot table data [infile] to a sound data",
	"     [outfile] showing the graph form by the musical tone.",
	" [1] if specifying -m2, output sound has multi channels as the same",
	"     number of graphs.",
	" [2] Invalid data will be translate to null sound.",
	" [3] Currently, 8 wave pattern are provide and used periodically",
	"     if the number of graphs > 8. Option -t is for selecting the",
	"     start wave pattern.",
	" [4] If using -l[n]b, sound length becomes [n]/(sampling rate) sec.",
	" [5] If using -u2, sounds for all graphs use the same wave pattern.",
	" [6] Tone limits may be specified by frequency value, or the",
	"     alphabetical name. The name is the following form:",
	"       [A-G](#|b)(integer)", 
	"     where '#' and 'b' are half tone signs, and the last integer is",
	"     the octave level. For example,",
	"       C0: 261.6256Hz,  C#0(=Db0): 277.1826Hz, ", 
	"       D0: 293.6648Hz,  D#0(=Eb0): 311.1270Hz, ", 
	"       E0: 329.6276Hz, ", 
	"       F0: 349.2282Hz,  F#0(=Gb0): 369.9944Hz", 
	"       G0: 391.9954Hz,  G#0(=Ab0): 415.3047Hz, ", 
	"       A0: 440.0000Hz,  A#0(=Bb0): 466.1638Hz, ", 
	"       B0: 493.8833Hz, ", 
	"       C1: 523.2511Hz, ", 
	NULL
    };
    int j;

#ifdef DEBUG1
    fprintf(stderr, "USAGE: %d\n", ret);
#endif
    fprintf(stderr, topline, comname);
    for (j = 0; mess[j] != NULL; j ++)
      fprintf(stderr, "%s\n", mess[j]);
}
