/*================================================================
 * sf2text -- print soundfont layer information
 *
 * Copyright (C) 1996,1997 Takashi Iwai
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include "timidity.h"
#include "common.h"
#include "sffile.h"
#include "sfitem.h"
#include "sflayer.h"
#include "controls.h"
#include "instrum.h"
#include "playmidi.h"
#include "output.h"
#include "wrd.h"

static SFInfo sfinfo;

void print_soundfont(FILE *fp, SFInfo *sf);
static void print_name(FILE *fp, char *str);
static void print_layers(FILE *fp, SFInfo *sf, SFHeader *hdr);
static void print_amount(FILE *fp, SFInfo *sf, SFGenRec *gen);

/*ARGSUSED*/
static int null_cmsg(int type, int verbosity_level, char *fmt, ...){return 0;}
static void null_proc(){}
static ControlMode null_control_mode =
{
    "", 0,
    1,0,0,
    0,
    NULL, null_proc, NULL, NULL, null_cmsg, null_proc
};

static PlayMode null_play_mode =
{
    8000, PE_MONO, 0,
    -1,
    {0,0,0,0,0},
    "", 0,
    "",
    NULL, NULL, NULL, NULL
};

/*ARGSUSED*/
static int  null_wrdt_open(char *wrdt_opts) { return 0; }
/*ARGSUSED*/
static void null_wrdt_apply(int cmd, int argc, int args[]) { }
static void null_wrdt_update_events(void) { }
static void null_wrdt_end(void) { }
static void null_wrdt_close(void) { }
static WRDTracer null_wrdt_mode =
{
    "No WRD trace", '-',
    0,
    null_wrdt_open,
    null_wrdt_apply,
    NULL,
    null_wrdt_update_events,
    NULL,
    null_wrdt_end,
    null_wrdt_close
};

PlayMode *play_mode = &null_play_mode;
ControlMode *ctl = &null_control_mode;
WRDTracer *wrdt = &null_wrdt_mode;
int progbase = 0;

extern struct URL_module URL_module_file;
#ifdef SUPPORT_SOCKET
extern struct URL_module URL_module_http;
extern struct URL_module URL_module_ftp;
extern struct URL_module URL_module_news;
#endif /* SUPPORT_SOCKET */

int main(int argc, char  **argv)
{
	struct timidity_file *fd;
	FILE *fout;

	if (argc < 2) {
		fprintf(stderr, "no file is given\n");
		fprintf(stderr, "usage: sf2text soundfont [outputfile]\n");
		exit(1);
	}

	url_add_modules(
		&URL_module_file,
#ifdef SUPPORT_SOCKET
		&URL_module_http,
		&URL_module_ftp,
		&URL_module_news,
#endif /* SUPPORT_SOCKET */
		NULL);

	if ((fd = open_file(argv[1], 1, 0)) == NULL) {
		fprintf(stderr, "can't open file %s\n", argv[1]);
		exit(1);
	}
	if (load_soundfont(&sfinfo, fd) < 0)
		exit(1);
	close_file(fd);

	if (argc < 3)
		fout = stdout;
	else {
		if ((fout = fopen(argv[2], "w")) == NULL) {
			fprintf(stderr, "can't open file %s\n", argv[2]);
			exit(1);
		}
	}
	print_soundfont(fout, &sfinfo);
	return 0;
}


void print_soundfont(FILE *fp, SFInfo *sf)
{
	int i;
	SFPresetHdr *preset;
	SFInstHdr *inst;
	SFSampleInfo *sp;

	fprintf(fp, "(Name ");
	print_name(fp, sf->sf_name);
	fprintf(fp, ")\n");

	fprintf(fp, "(SoundFont %d %d)\n", sf->version, sf->minorversion);
	fprintf(fp, "(SamplePos %ld %ld)\n", (long)sf->samplepos, (long)sf->samplesize);
	fprintf(fp, "(InfoPos %ld %ld)\n", sf->infopos, sf->infosize);
	fprintf(fp, "(Presets %d (\n", sf->npresets);
	for (preset = sf->preset, i = 0; i < sf->npresets; preset++, i++) {
		fprintf(fp, " (");
		fprintf(fp, "%d ", i);
		print_name(fp, preset->hdr.name);
		fprintf(fp, " (preset %d) (bank %d) (\n",
		       preset->preset, preset->bank);
		print_layers(fp, sf, &preset->hdr);
		fprintf(fp, "  ))\n");
	}
	fprintf(fp, " ))\n");
	
	fprintf(fp, "(Instruments %d (\n", sf->ninsts);
	for (inst = sf->inst, i = 0; i < sf->ninsts; inst++, i++) {
		fprintf(fp, "  (");
		fprintf(fp, "%d ", i);
		print_name(fp, inst->hdr.name);
		fprintf(fp, " (\n");
		print_layers(fp, sf, &inst->hdr);
		fprintf(fp, "  ))\n");
	}
	fprintf(fp, " ))\n");
	
	fprintf(fp, "(SampleInfo %d (\n", sf->nsamples);
	for (sp = sf->sample, i = 0; i < sf->nsamples; sp++, i++) {
		fprintf(fp, " (%d ", i);
		print_name(fp, sp->name);
		fprintf(fp, " (0x%lx 0x%lx) (0x%lx 0x%lx)",
		       (long)sp->startsample, (long)sp->endsample,
		       (long)sp->startloop, (long)sp->endloop);
		if (sf->version == 2) {
			fprintf(fp, "\n          (%ld %d %d %d %d)",
			       (long)sp->samplerate, sp->originalPitch,
			       sp->pitchCorrection, sp->samplelink,
			       sp->sampletype);
		}
		fprintf(fp, ")\n");
	}
	fprintf(fp, " ))\n");
}

/* print string value. escape or convert the letter to octet if necessary. */
static void print_name(FILE *fp, char *str)
{
	char *p;
	putc('"', fp);
	for (p = str; *p; p++) {
		if (!isprint((int)*p))
			fprintf(fp, "\\%03o", *p);
		else if (*p == '"')
			fprintf(fp, "\\\"");
		else
			putc(*p, fp);
	}
	
	putc('"', fp);
}

/* print layered list */
static void print_layers(FILE *fp, SFInfo *sf, SFHeader *hdr)
{
	SFGenLayer *lay = hdr->layer;
	int j, k;

	for (j = 0; j < hdr->nlayers; lay++, j++) {
		if (lay->nlists == 0) continue;
		fprintf(fp, "  (layer\n");
		for (k = 0; k < lay->nlists; k++) {
			print_amount(fp, sf, &lay->list[k]);
			if (k == lay->nlists-1)
				fprintf(fp, ")\n");
			else
				fprintf(fp, "\n");
		}
	}
}

static int abscent_to_mHz(int abscents)
{
    return (int)(8176.0 * pow(2.0, (double)abscents / 1200.0));
}

int abscent_to_Hz(int abscents)
{
    return (int)(8.176 * pow(2.0, (double)abscents / 1200.0));
}

static int timecent_to_msec(int timecent)
{
    return (int)(1000 * pow(2.0, (double)timecent / 1200.0));
}

/* print a layer item together with optional info */
static void print_amount(FILE *fp, SFInfo *sf, SFGenRec *gen)
{
	LayerItem *item = &layer_items[gen->oper];
	int amount;

	fprintf(fp, "   (%s %d", sf_gen_text[gen->oper], gen->amount);

	if (sf->version == 1)
		amount = sbk_to_sf2(gen->oper, gen->amount);
	else
		amount = gen->amount;

	switch (item->type) {
	case T_NOP:
	case T_NOCONV:
	case T_OFFSET:
	case T_HI_OFF:
	case T_SCALE:
		fprintf(fp, " ");
		if (gen->oper == SF_sampleId)
			print_name(fp, sf->sample[amount].name);
		else if (gen->oper == SF_instrument)
			print_name(fp, sf->inst[amount].hdr.name);
		else 
			fprintf(fp, "%d", amount);
		break;
	case T_RANGE:
		fprintf(fp, " (%d %d)", LOWNUM(amount), HIGHNUM(amount));
		break;
	case T_FILTERQ:
	case T_ATTEN:
	case T_TREMOLO:
	case T_VOLSUST:
		fprintf(fp, " %d cB", amount);
		break;
	case T_MODSUST:
		fprintf(fp, " %g %%", (double)amount/10.0);
		break;
	case T_CUTOFF:
		fprintf(fp, " %d Hz", abscent_to_Hz(amount));
		break;
	case T_FREQ:
		fprintf(fp, " %d mHz", abscent_to_mHz(amount));
		break;
	case T_TIME:
		fprintf(fp, " %d msec", timecent_to_msec(amount));
		break;
	case T_TENPCT:
	case T_PANPOS:
		fprintf(fp, " %g %%", (double)amount / 10.0);
		break;
	case T_PSHIFT:
	case T_CSHIFT:
		fprintf(fp, " %g semitone", (double)amount / 100);
		break;
	}
	fprintf(fp, ")");
}
