/*

    TiMidity -- Experimental MIDI to WAVE converter
    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>

	 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.

*/
#include <stdio.h>
#include <stdlib.h>

#include <string.h>

#if defined(__linux__) || defined(__FreeBSD__)
#    ifndef AU_LINUX
#        define AU_LINUX
#    endif
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>

#include <errno.h>
#include "gtim.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "readmidi.h"
#include "output.h"
#include "controls.h"
#include "tables.h"

int     free_instruments_afterwards = 0;
int     cfg_select = 0;

int     have_commandline_midis = 0;
int     output_device_open = 0;


static void
help (void)
{
    PlayMode **pmp = play_mode_list;
    ControlMode **cmp = ctl_list;
    cmp = cmp;
    printf (PACKAGE_STRING ", from TiMidity version 0.2i (C) 1995 Tuukka Toivonen "
	    "<toivonen@clinet.fi>\n"
	    " TiMidity is free software and comes with ABSOLUTELY NO WARRANTY.\n"
	    "\n"
	    "Usage:\n"
	    "  %s [options] [directory | filenames ...] [options]\n"
	    "Options:\n"
	    " -o file Output file         -O mode Output mode (see below)\n"
	    " -s f    Sampling frequency  -a      Enable antialiasing filter\n"
#ifdef FAST_DECAY
	    " -f      Disable fast decay  -d      dry mode\n"
#else
	    " -f      Enable fast decay   -d      dry mode\n"
#endif
	    " -p n    N-voice polyphony   -A n    Amplify volume by n percent\n"
	    " -C n    Control freq ratio  -# n    Select patch set\n"
	    " -L dir  Search path         -c file Read config file\n"
	    " -I n    Default program     -b n	  Use bank n for bank 0\n"
	    " -D n    Drums on channel n  -Q n    Ignore channel n\n"
	    " -R n    Reverb [%2d]         -k n    Interpolation (0-3) [%d]\n"
	    " -r n    Patch ram [%3dM]    -X n    Linear/exp expression (0-5) [%d]\n"
	    " -F      Fast load [%d]       -V n    Linear/exp volume (0-5) [%d]\n"
	    " -U      Unload instruments  -i mode User interface (see below)\n"
	    " -B n    Buffer fragments    -v      Interesting message\n"
	    " -h      Help message        -S n    Surround separation (64-95)\n"
	    , program_name,
	    reverb_options,
	    current_interpolation, max_patch_memory / 1000000,
	    opt_expression_curve, fast_load, opt_volume_curve);

    printf ("Available output modes (-O option):\n");
    while (*pmp) {
	printf (" -O%c     %s", (*pmp)->id_character, (*pmp)->id_name);
	pmp++;
	if (*pmp) {
		printf ("     -O%c     %s\n", (*pmp)->id_character, (*pmp)->id_name);
		pmp++;
	}
	else printf("\n");
    }
    printf ("Output format options (append to -O? option):\n"
	    " `8'     8-bit samples  `1'  16-bit samples  `3'  24-bit samples\n"
	    " `U'     uLaw encoding  `l'  linear encoding `M'  monophonic\n"
	    " `S'     stereo         `s'  signed output   `u'  unsigned output\n"
	    " 'q'     quadraphonic   `x'  byte-swapped output\n");

    printf ("Available interfaces (-i option):\n");
    while (*cmp) {
	printf (" -i%c     %s  ", (*cmp)->id_character, (*cmp)->id_name);
	cmp++;
    }
    printf ("\nInterface options (append to -i? option):\n"
	    " `v'     verbose (cumulative)  `q' quiet (cumulative) `t' trace\n");
}

static void
interesting_message (void)
{
    printf ("\n"
	    " TiMidity version 0.2i -- Experimental MIDI to WAVE converter\n"
	    " Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>\n"
	    " \n"
	    " This program is free software; you can redistribute it and/or modify\n"
	    " it under the terms of the GNU General Public License as published by\n"
	    " the Free Software Foundation; either version 2 of the License, or\n"
	    " (at your option) any later version.\n"
	    " \n"
	    " This program is distributed in the hope that it will be useful,\n"
	    " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
	    " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
	    " GNU General Public License for more details.\n"
	    " \n"
	    " You should have received a copy of the GNU General Public License\n"
	    " along with this program; if not, write to the Free Software\n"
	    " Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n"
	    "\n");

}

static int
set_channel_flag (int32 *flags, int32 i, const char *name)
{
    if (i == 0)
	*flags = 0;
    else if ((i < 1 || i > 16) && (i < -16 || i > -1)) {
	fprintf (stderr,
		 "%s must be between 1 and 16, or between -1 and -16, or 0\n",
		 name);
	return -1;
    }
    else {
	if (i > 0)
	    *flags |= (1 << (i - 1));
	else
	    *flags &= ~((1 << (-1 - i)));
    }
    return 0;
}

static int
set_value (int32 *param, int32 i, int32 low, int32 high, const char *name)
{
    if (i < low || i > high) {
	fprintf (stderr, "%s must be between %d and %d\n", name, low, high);
	return -1;
    }
    else
	*param = i;
    return 0;
}

int
set_play_mode (char *cp)
{
    PlayMode *pmp, **pmpp = play_mode_list;

    while ((pmp = *pmpp++)) {
	if (pmp->id_character == *cp) {
	    play_mode = pmp;
	    while (*(++cp))
		switch (*cp) {
		case 'U':
		    pmp->encoding |= PE_ULAW;
		    break;	/* uLaw */
		case 'l':
		    pmp->encoding &= ~PE_ULAW;
		    break;	/* linear */
		case '1':
		    pmp->encoding |= PE_16BIT;
		    break;	/* 1 for 16-bit */
		case '8':
		    pmp->encoding &= ~PE_16BIT;
		    break;
		case '3':
		    pmp->encoding &= ~PE_16BIT;
		    pmp->encoding |= PE_24BIT;
		    break;

		case 'M':
		    pmp->encoding |= PE_MONO;
		    requested_ochannels = 1;
		    break;
		case 'S':
		    pmp->encoding &= ~PE_MONO;
		    requested_ochannels = 2;
		    break;	/* stereo */
		case 'q':
		    requested_ochannels = 4;
		    break;
		case 's':
		    pmp->encoding |= PE_SIGNED;
		    break;
		case 'u':
		    pmp->encoding &= ~PE_SIGNED;
		    break;

		case 'x':
		    pmp->encoding ^= PE_BYTESWAP;
		    break;	/* toggle */

		default:
		    fprintf (stderr, "Unknown format modifier `%c'\n", *cp);
		    return 1;
		}
	    return 0;
	}
    }

    fprintf (stderr, "Playmode `%c' is not compiled in.\n", *cp);
    return 1;
}

static int
set_ctl (char *cp)
{
    ControlMode *cmp, **cmpp = ctl_list;

    while ((cmp = *cmpp++)) {
	if (cmp->id_character == *cp) {
	    ctl = cmp;
	    while (*(++cp))
		switch (*cp) {
		case 'v':
		    cmp->verbosity++;
		    break;
		case 'q':
		    cmp->verbosity--;
		    break;
		case 't':	/* toggle */
		    cmp->trace_playing = (cmp->trace_playing) ? 0 : 1;
		    break;

		default:
		    fprintf (stderr, "Unknown interface option `%c'\n", *cp);
		    return 1;
		}
	    return 0;
	}
    }

    fprintf (stderr, "Interface `%c' is not compiled in.\n", *cp);
    return 1;
}

char   *cfg_names[30];


void
clear_config (void)
{
    ToneBank *bank = 0;
    int     i, j;

    for (i = 0; i < MAXBANK; i++) {
	if (tonebank[i]) {
	    bank = tonebank[i];
	    if (bank->name)
		free ((void *) bank->name);
	    bank->name = 0;
	    for (j = 0; j < MAXPROG; j++)
		if (bank->tone[j].name) {
		    free ((void *) bank->tone[j].name);
		    bank->tone[j].name = 0;
		}
	    if (i > 0) {
		free (tonebank[i]);
		tonebank[i] = 0;
	    }
	}
	if (drumset[i]) {
	    bank = drumset[i];
	    if (bank->name)
		free ((void *) bank->name);
	    bank->name = 0;
	    for (j = 0; j < MAXPROG; j++)
		if (bank->tone[j].name) {
		    free ((void *) bank->tone[j].name);
		    bank->tone[j].name = 0;
		}
	    if (i > 0) {
		free (drumset[i]);
		drumset[i] = 0;
	    }
	}
    }

    memset (drumset[0], 0, sizeof (ToneBank));
    memset (tonebank[0], 0, sizeof (ToneBank));
    clear_pathlist (0);
}

static int control_device_open = 0;


static void smart_pass_playing_list (int file_count, const char **file_list)
{
    struct dirent **namelist;
    char **new_list;
    char *new_name;
    char new_path[512];
    const char *which_dir;
    int n, i, fd;
    char fbuf[12];
    struct stat info;

    which_dir = ".";

    if (file_count == 1) {
	    stat (file_list[0], &info);
	    if ( S_ISDIR(info.st_mode) ) {
		    which_dir = file_list[0];
		    file_count = 0;
	    }
    }

    if (!file_count) {
           n = scandir(which_dir, &namelist, 0, alphasort);
           if (n < 0)
               perror("scandir");
           else if (n > 0) {
	       int num_to_free = n;
	       new_list = (char **)malloc(n * sizeof (char *));
	       for (i = 0; n; i++, n--) {
		   new_name = namelist[i]->d_name;
		   if (!strcmp(new_name, ".") || !strcmp(new_name, ".."))
			   continue;
		   if ( !strcmp(which_dir, ".") ) strcpy(new_path, new_name);
		   else {
		       strcpy(new_path, which_dir);
		       if ( which_dir[ strlen(which_dir) - 1] != '/') strcat(new_path, "/");
		       strcat(new_path, new_name);
		   }
		   if (stat (new_path, &info)) continue;
		   if ( !S_ISREG(info.st_mode) ) continue;
		   if ( info.st_size < 100 ) continue;
		   if ( (fd = open (new_path, O_RDONLY)) < 0 ) continue;
		   if ( read(fd, fbuf, 12) < 12 ) { close(fd); continue; }
		   close(fd);
		   if ( memcmp (fbuf, "MThd", 4) ) {
		   	if ( memcmp (fbuf, "RIFF", 4) ) continue;
		   	if ( memcmp (fbuf + 8, "RMID", 4) ) continue;
		   }
		   new_list[file_count] = (char *)malloc( 1 + strlen(new_path) );
		   strcpy (new_list[file_count], new_path);
		   file_count++;
               }
	       for (i = 0; i < num_to_free; i++) free(namelist[i]);
               free(namelist);
	       file_list = (const char **)new_list;
           }
    }

    if (!file_count) {
	    if (control_device_open) ctl->close();
	    control_device_open = 0;
	    fprintf(stderr, "Please cd to a directory with some midi files.\n");
	    return;
    }
    ctl->pass_playing_list (file_count, file_list);
}

int     reverb_options = OPT_REVERB_VOICE | OPT_CHORUS_VOICE | OPT_STEREO_VOICE | OPT_CELESTE_VOICE;
int     global_reverb = 0;
int     global_chorus = 0;
int     global_echo = 0;
int     global_detune = 0;

int     got_a_configuration = 0;


int
main (int argc, char **argv)
{
    int     c,
	cmderr = 0, i, try_config_again = 0, need_stdin = 0, need_stdout = 0;
    int     orig_optind;

    int32   tmpi32, output_rate = 0;

    char   *output_name = 0;

    int     buffer_fragments = -1;

#ifdef DEFAULT_PATH
    add_to_pathlist (DEFAULT_PATH, 0);
#endif

    if ((program_name = rindex (argv[0], '/')))
	program_name++;
    else
	program_name = argv[0];


    while ((c = getopt (argc, argv, "vUI:L:b:c:A:C:aFp:fo:O:s:Q:R:S:D:hi:#:k:r:X:V:d-::" "B:"	/* buffer fragments */
	    )) > 0)
	switch (c) {
	case 'v':
	    interesting_message ();
	    return 0;
	case 'U':
	    free_instruments_afterwards = 1;
	    break;
	case 'L':
	    add_to_pathlist (optarg, 0);
	    try_config_again = 1;
	    break;
	case 'b':
	    opt_bank = atoi(optarg);
	    break;
	case 'c':
	    if (read_config_file (optarg, 1))
		cmderr++;
	    break;

	case 'Q':
	    if (set_channel_flag
		(&quietchannels, atoi (optarg), "Quiet channel"))
		cmderr++;
	    break;

	case 'k':
	    i = atoi (optarg);
	    switch (i) {
		    case 0: current_interpolation = INTERPOLATION_LINEAR;
			    break;
		    case 1: current_interpolation = INTERPOLATION_CSPLINE;
			    break;
		    case 2: current_interpolation = INTERPOLATION_LAGRANGE;
			    break;
		    default:
		    case 3: current_interpolation = INTERPOLATION_CSPLINE | INTERPOLATION_BUTTERWORTH;
			    break;
	    }
	    break;

	case 'd':
	    opt_dry = 1;
	    break;

	case 'F':
	    fast_load = !fast_load;
	    break;

	case 'X':
	    opt_expression_curve = atoi (optarg);
	    break;

	case 'V':
	    opt_volume_curve = atoi (optarg);
	    break;

	case 'r':
	    max_patch_memory = atoi (optarg) * 1000000;
	    break;

	case 'R':
	    reverb_options = atoi (optarg);
	    break;

	case 'D':
	    if (set_channel_flag
		(&drumchannels, atoi (optarg), "Drum channel"))
		cmderr++;
	    break;

	case 'O':		/* output mode */
	    if (set_play_mode (optarg))
		cmderr++;
	    break;

	case 'o':
	    output_name = optarg;
	    break;

	case 'a':
	    antialiasing_allowed = 1;
	    break;

	case 'f':
	    fast_decay = (fast_decay) ? 0 : 1;
	    break;

#if 0
	case 'F':
	    adjust_panning_immediately = 1;
	    break;
#endif

	case 'S':
	    wide_panning = atoi (optarg);
	    break;

	case 's':		/* sampling rate */
	    i = atoi (optarg);
	    if (i < 100)
		i *= 1000;
	    if (set_value (&output_rate, i, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE,
			   "Resampling frequency"))
		cmderr++;
	    break;

	case 'I':
	    if (set_value (&tmpi32, atoi (optarg), 0, 127, "Default program"))
		cmderr++;
	    else
		default_program = tmpi32;
	    break;
	case 'A':
	    if (set_value
		(&amplification, atoi (optarg), 1, MAX_AMPLIFICATION,
		 "Amplification"))
		cmderr++;
	    break;
	case 'C':
	    if (set_value
		(&control_ratio, atoi (optarg), 1, MAX_CONTROL_RATIO,
		 "Control ratio"))
		cmderr++;
	    break;
	case 'p':
	    if (set_value (&tmpi32, atoi (optarg), 1, MAX_VOICES,
			   "Polyphony"))
		cmderr++;
	    else
		voices_ceiling = tmpi32;
	    break;

	case 'i':
	    if (set_ctl (optarg))
		cmderr++;
	    break;


	case 'B':
	    if (set_value (&tmpi32, atoi (optarg), 0, 1000,
			   "Buffer fragments"))
		cmderr++;
	    else
		buffer_fragments = tmpi32;
	    break;
	case '#':
	    cfg_select = atoi (optarg);
	    break;
	case 'h':
	    help ();
	    if (!read_config_file ("timidity.cfg", 1)) {
		int     cname;
		if (current_config_file) {
#ifdef DEFAULT_PATH
		    printf ("\nDefault config file is %s/%s\n", DEFAULT_PATH,
			    current_config_file);
#else
		    printf ("\nDefault config file is %s\n",
			    current_config_file);
#endif
		}
		for (cname = 0; cname < 30; cname++)
		    if (cfg_names[cname])
			printf ("Config option #%d is %s\n", cname,
				cfg_names[cname]);
	    }
	    return 0;

	case '-':
	    break;

	default:
	    cmderr++;
	    break;
	}

    try_config_again = 1;
/* don't use unnecessary memory until we're a child process */
    if (!got_a_configuration) {
	if (!try_config_again || read_config_file ("timidity.cfg", 1))
	    cmderr++;
    }

    /* If there were problems, give up now */
    if (cmderr) {
	fprintf (stderr, "Try %s -h for help\n", program_name);
	return 1;		/* problems with command line */
    }

    /* Set play mode parameters */
    if (output_rate)
	play_mode->rate = output_rate;
    if (output_name) {
	play_mode->name = output_name;
	if (!strcmp (output_name, "-"))
	    need_stdout = 1;
    }


    if (buffer_fragments != -1)
	play_mode->extra_param[0] = buffer_fragments;

    init_tables ();

    orig_optind = optind;

    while (optind < argc)
	if (!strcmp (argv[optind++], "-"))
	    need_stdin = 1;
    optind = orig_optind;

    if (argc - optind > 0)
	have_commandline_midis = argc - optind;
    else
	have_commandline_midis = 0;

    if (have_commandline_midis == 1 && !output_name &&
		    (play_mode->id_character == 'd' ||
		     play_mode->id_character == 'w')) {
	    output_name = (char *)malloc(strlen(argv[optind]) + 4);
	    if (strrchr(argv[optind], '/'))
	        strcpy(output_name, strrchr(argv[optind],'/') + 1);
	    else strcpy(output_name, argv[optind]);
            if (!strcmp (output_name + strlen(output_name) - 4, ".mid"))
		    output_name[strlen(output_name) - 4] = '\0';
	    if (play_mode->id_character == 'w')
		    strcat(output_name, ".wav");
	    else strcat(output_name, ".ac3");
	    play_mode->name = output_name;
    }

    if (play_mode->open_output () < 0) {
	output_device_open = 0;
	fprintf (stderr, "Couldn't open %s\n", play_mode->id_name);
	return 3;
    }
    else
	output_device_open = 1;

    /*
     * If open_output() did not set num_ochannels to 4 or 6, we
     * have just 1 or 2 output channels, depending on the encoding.
     * Assume we compute the same number in the audio buffer unless
     * a compute_data function changes num_ochannels_in_buf.
     * A mismatch between the number of channels computed in the
     * audio buffer and the number of output channels is the
     * responsibility of the play_mode output routine to take
     * care of.
     */
    if ((play_mode->encoding & PE_MONO))
	num_ochannels_in_buf = 1;
    else
	num_ochannels_in_buf = 2;
    if (num_ochannels < 4)
	num_ochannels = num_ochannels_in_buf;

    if (ctl->open (need_stdin, need_stdout)) {
	fprintf (stderr, "Couldn't open %s\n", ctl->id_name);
	play_mode->close_output ();
	return 3;
    }
    else control_device_open = 1;

    if (wide_panning < 0) {
	    if (num_ochannels == 6)
#ifdef NEW_PANNER
		    wide_panning = 32;
#else
		    wide_panning = 64;
#endif
	    else wide_panning = 95;
    }

    if (!control_ratio) {
	control_ratio = play_mode->rate / CONTROLS_PER_SECOND;
	if (control_ratio < 1)
	    control_ratio = 1;
	else if (control_ratio > MAX_CONTROL_RATIO)
	    control_ratio = MAX_CONTROL_RATIO;
    }

    if (got_a_configuration < 2)
	read_config_file (current_config_file, 0);

    smart_pass_playing_list (argc - optind,
			    (const char **) &argv[orig_optind]);

    if (output_device_open) play_mode->close_output ();
    if (control_device_open) ctl->close ();

    return 0;
}
