qzdoom/libraries/timidityplus/configfile.cpp
Christoph Oelckers 7468c0f36d - consolidated the different file access interfaces in the backends into one shared version.
This was getting a bit unwieldy. The include path setup is not perfect yet, that's work for later.
(It's about time we're getting C++20 with modules so that this include path madness can be put to an end.)
2019-09-28 23:17:16 +02:00

1612 lines
38 KiB
C++

/*
TiMidity++ -- MIDI to WAVE converter and player
Copyright (C) 1999-2008 Masanao Izumo <iz@onicos.co.jp>
Copyright (C) 1995 Tuukka Toivonen <tt@cgs.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "quantity.h"
namespace TimidityPlus
{
#define MAXWORDS 130
#define CHECKERRLIMIT \
if(++errcnt >= 10) { \
printMessage(CMSG_ERROR, VERB_NORMAL, \
"Too many errors... Give up read %s", name); \
reuse_mblock(&varbuf); \
tf_close(tf); return 1; }
typedef struct {
const char *name;
int mapid, isdrum;
} MapNameEntry;
static int mapnamecompare(const void *name, const void *entry)
{
return strcmp((const char *)name, ((const MapNameEntry *)entry)->name);
}
static int mapname2id(char *name, int *isdrum)
{
static const MapNameEntry data[] = {
/* sorted in alphabetical order */
{ "gm2", GM2_TONE_MAP, 0 },
{ "gm2drum", GM2_DRUM_MAP, 1 },
{ "sc55", SC_55_TONE_MAP, 0 },
{ "sc55drum", SC_55_DRUM_MAP, 1 },
{ "sc88", SC_88_TONE_MAP, 0 },
{ "sc8850", SC_8850_TONE_MAP, 0 },
{ "sc8850drum", SC_8850_DRUM_MAP, 1 },
{ "sc88drum", SC_88_DRUM_MAP, 1 },
{ "sc88pro", SC_88PRO_TONE_MAP, 0 },
{ "sc88prodrum", SC_88PRO_DRUM_MAP, 1 },
{ "xg", XG_NORMAL_MAP, 0 },
{ "xgdrum", XG_DRUM_MAP, 1 },
{ "xgsfx126", XG_SFX126_MAP, 1 },
{ "xgsfx64", XG_SFX64_MAP, 0 }
};
const MapNameEntry *found;
found = (MapNameEntry *)bsearch(name, data, sizeof data / sizeof data[0], sizeof data[0], mapnamecompare);
if (found != NULL)
{
*isdrum = found->isdrum;
return found->mapid;
}
return -1;
}
static float *config_parse_tune(const char *cp, int *num)
{
const char *p;
float *tune_list;
int i;
/* count num */
*num = 1, p = cp;
while ((p = strchr(p, ',')) != NULL)
(*num)++, p++;
/* alloc */
tune_list = (float *)safe_malloc((*num) * sizeof(float));
/* regist */
for (i = 0, p = cp; i < *num; i++, p++) {
tune_list[i] = atof(p);
if (!(p = strchr(p, ',')))
break;
}
return tune_list;
}
static int16_t *config_parse_int16(const char *cp, int *num)
{
const char *p;
int16_t *list;
int i;
/* count num */
*num = 1, p = cp;
while ((p = strchr(p, ',')) != NULL)
(*num)++, p++;
/* alloc */
list = (int16_t *)safe_malloc((*num) * sizeof(int16_t));
/* regist */
for (i = 0, p = cp; i < *num; i++, p++) {
list[i] = atoi(p);
if (!(p = strchr(p, ',')))
break;
}
return list;
}
static int **config_parse_envelope(const char *cp, int *num)
{
const char *p, *px;
int **env_list;
int i, j;
/* count num */
*num = 1, p = cp;
while ((p = strchr(p, ',')) != NULL)
(*num)++, p++;
/* alloc */
env_list = (int **)safe_malloc((*num) * sizeof(int *));
for (i = 0; i < *num; i++)
env_list[i] = (int *)safe_malloc(6 * sizeof(int));
/* init */
for (i = 0; i < *num; i++)
for (j = 0; j < 6; j++)
env_list[i][j] = -1;
/* regist */
for (i = 0, p = cp; i < *num; i++, p++) {
px = strchr(p, ',');
for (j = 0; j < 6; j++, p++) {
if (*p == ':')
continue;
env_list[i][j] = atoi(p);
if (!(p = strchr(p, ':')))
break;
if (px && p > px)
break;
}
if (!(p = px))
break;
}
return env_list;
}
static Quantity **config_parse_modulation(const char *name, int line, const char *cp, int *num, int mod_type)
{
const char *p, *px, *err;
char buf[128], *delim;
Quantity **mod_list;
int i, j;
static const char * qtypestr[] = { "tremolo", "vibrato" };
static const uint16_t qtypes[] = {
QUANTITY_UNIT_TYPE(TREMOLO_SWEEP), QUANTITY_UNIT_TYPE(TREMOLO_RATE), QUANTITY_UNIT_TYPE(DIRECT_INT),
QUANTITY_UNIT_TYPE(VIBRATO_SWEEP), QUANTITY_UNIT_TYPE(VIBRATO_RATE), QUANTITY_UNIT_TYPE(DIRECT_INT)
};
/* count num */
*num = 1, p = cp;
while ((p = strchr(p, ',')) != NULL)
(*num)++, p++;
/* alloc */
mod_list = (Quantity **)safe_malloc((*num) * sizeof(Quantity *));
for (i = 0; i < *num; i++)
mod_list[i] = (Quantity *)safe_malloc(3 * sizeof(Quantity));
/* init */
for (i = 0; i < *num; i++)
for (j = 0; j < 3; j++)
INIT_QUANTITY(mod_list[i][j]);
buf[sizeof buf - 1] = '\0';
/* regist */
for (i = 0, p = cp; i < *num; i++, p++) {
px = strchr(p, ',');
for (j = 0; j < 3; j++, p++) {
if (*p == ':')
continue;
if ((delim = strpbrk(strncpy(buf, p, sizeof buf - 1), ":,")) != NULL)
*delim = '\0';
if (*buf != '\0' && (err = string_to_quantity(buf, &mod_list[i][j], qtypes[mod_type * 3 + j])) != NULL) {
printMessage(CMSG_ERROR, VERB_NORMAL, "%s: line %d: %s: parameter %d of item %d: %s (%s)",
name, line, qtypestr[mod_type], j + 1, i + 1, err, buf);
free_ptr_list(mod_list, *num);
mod_list = NULL;
*num = 0;
return NULL;
}
if (!(p = strchr(p, ':')))
break;
if (px && p > px)
break;
}
if (!(p = px))
break;
}
return mod_list;
}
/*! copy bank and, if necessary, map appropriately */
void Instruments::copybank(ToneBank *to, ToneBank *from, int mapid, int bankmapfrom, int bankno)
{
ToneBankElement *toelm, *fromelm;
int i;
if (from == NULL)
return;
for (i = 0; i < 128; i++)
{
toelm = &to->tone[i];
fromelm = &from->tone[i];
if (fromelm->name == NULL)
continue;
copy_tone_bank_element(toelm, fromelm);
toelm->instrument = NULL;
if (mapid != INST_NO_MAP)
set_instrument_map(mapid, bankmapfrom, i, bankno, i);
}
}
/*! copy the whole mapped bank. returns 0 if no error. */
int Instruments::copymap(int mapto, int mapfrom, int isdrum)
{
ToneBank **tb = isdrum ? drumset : tonebank;
int i, bankfrom, bankto;
for (i = 0; i < 128; i++)
{
bankfrom = find_instrument_map_bank(isdrum, mapfrom, i);
if (bankfrom <= 0) /* not mapped */
continue;
bankto = alloc_instrument_map_bank(isdrum, mapto, i);
if (bankto == -1) /* failed */
return 1;
copybank(tb[bankto], tb[bankfrom], mapto, i, bankto);
}
return 0;
}
int Instruments::set_gus_patchconf_opts(const char *name, int line, char *opts, ToneBankElement *tone)
{
char *cp;
int k;
if (!(cp = strchr(opts, '='))) {
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: bad patch option %s", name, line, opts);
return 1;
}
*cp++ = 0;
if (!strcmp(opts, "amp")) {
k = atoi(cp);
if ((k < 0 || k > MAX_AMPLIFICATION) || (*cp < '0' || *cp > '9')) {
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: amplification must be between 0 and %d",
name, line, MAX_AMPLIFICATION);
return 1;
}
tone->amp = k;
}
else if (!strcmp(opts, "note")) {
k = atoi(cp);
if ((k < 0 || k > 127) || (*cp < '0' || *cp > '9')) {
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: note must be between 0 and 127",
name, line);
return 1;
}
tone->note = k;
tone->scltune = config_parse_int16("100", &tone->scltunenum);
}
else if (!strcmp(opts, "pan")) {
if (!strcmp(cp, "center"))
k = 64;
else if (!strcmp(cp, "left"))
k = 0;
else if (!strcmp(cp, "right"))
k = 127;
else {
k = ((atoi(cp) + 100) * 100) / 157;
if ((k < 0 || k > 127)
|| (k == 0 && *cp != '-' && (*cp < '0' || *cp > '9'))) {
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: panning must be left, right, "
"center, or between -100 and 100",
name, line);
return 1;
}
}
tone->pan = k;
}
else if (!strcmp(opts, "tune"))
tone->tune = config_parse_tune(cp, &tone->tunenum);
else if (!strcmp(opts, "rate"))
tone->envrate = config_parse_envelope(cp, &tone->envratenum);
else if (!strcmp(opts, "offset"))
tone->envofs = config_parse_envelope(cp, &tone->envofsnum);
else if (!strcmp(opts, "keep")) {
if (!strcmp(cp, "env"))
tone->strip_envelope = 0;
else if (!strcmp(cp, "loop"))
tone->strip_loop = 0;
else {
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: keep must be env or loop", name, line);
return 1;
}
}
else if (!strcmp(opts, "strip")) {
if (!strcmp(cp, "env"))
tone->strip_envelope = 1;
else if (!strcmp(cp, "loop"))
tone->strip_loop = 1;
else if (!strcmp(cp, "tail"))
tone->strip_tail = 1;
else {
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: strip must be env, loop, or tail",
name, line);
return 1;
}
}
else if (!strcmp(opts, "tremolo")) {
if ((tone->trem = config_parse_modulation(name,
line, cp, &tone->tremnum, 0)) == NULL)
return 1;
}
else if (!strcmp(opts, "vibrato")) {
if ((tone->vib = config_parse_modulation(name,
line, cp, &tone->vibnum, 1)) == NULL)
return 1;
}
else if (!strcmp(opts, "sclnote"))
tone->sclnote = config_parse_int16(cp, &tone->sclnotenum);
else if (!strcmp(opts, "scltune"))
tone->scltune = config_parse_int16(cp, &tone->scltunenum);
else if (!strcmp(opts, "comm")) {
char *p;
if (tone->comment)
free(tone->comment);
p = tone->comment = safe_strdup(cp);
while (*p) {
if (*p == ',')
*p = ' ';
p++;
}
}
else if (!strcmp(opts, "modrate"))
tone->modenvrate = config_parse_envelope(cp, &tone->modenvratenum);
else if (!strcmp(opts, "modoffset"))
tone->modenvofs = config_parse_envelope(cp, &tone->modenvofsnum);
else if (!strcmp(opts, "envkeyf"))
tone->envkeyf = config_parse_envelope(cp, &tone->envkeyfnum);
else if (!strcmp(opts, "envvelf"))
tone->envvelf = config_parse_envelope(cp, &tone->envvelfnum);
else if (!strcmp(opts, "modkeyf"))
tone->modenvkeyf = config_parse_envelope(cp, &tone->modenvkeyfnum);
else if (!strcmp(opts, "modvelf"))
tone->modenvvelf = config_parse_envelope(cp, &tone->modenvvelfnum);
else if (!strcmp(opts, "trempitch"))
tone->trempitch = config_parse_int16(cp, &tone->trempitchnum);
else if (!strcmp(opts, "tremfc"))
tone->tremfc = config_parse_int16(cp, &tone->tremfcnum);
else if (!strcmp(opts, "modpitch"))
tone->modpitch = config_parse_int16(cp, &tone->modpitchnum);
else if (!strcmp(opts, "modfc"))
tone->modfc = config_parse_int16(cp, &tone->modfcnum);
else if (!strcmp(opts, "fc"))
tone->fc = config_parse_int16(cp, &tone->fcnum);
else if (!strcmp(opts, "q"))
tone->reso = config_parse_int16(cp, &tone->resonum);
else if (!strcmp(opts, "fckeyf")) /* filter key-follow */
tone->key_to_fc = atoi(cp);
else if (!strcmp(opts, "fcvelf")) /* filter velocity-follow */
tone->vel_to_fc = atoi(cp);
else if (!strcmp(opts, "qvelf")) /* resonance velocity-follow */
tone->vel_to_resonance = atoi(cp);
else {
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: bad patch option %s",
name, line, opts);
return 1;
}
return 0;
}
void Instruments::reinit_tone_bank_element(ToneBankElement *tone)
{
free_tone_bank_element(tone);
tone->note = tone->pan = -1;
tone->strip_loop = tone->strip_envelope = tone->strip_tail = -1;
tone->amp = -1;
tone->rnddelay = 0;
tone->loop_timeout = 0;
tone->legato = tone->damper_mode = tone->key_to_fc = tone->vel_to_fc = 0;
tone->reverb_send = tone->chorus_send = tone->delay_send = -1;
tone->tva_level = -1;
tone->play_note = -1;
}
int Instruments::set_gus_patchconf(const char *name, int line, ToneBankElement *tone, char *pat, char **opts)
{
int j;
reinit_tone_bank_element(tone);
if (strcmp(pat, "%font") == 0) /* Font extention */
{
/* %font filename bank prog [note-to-use]
* %font filename 128 bank key
*/
if (opts[0] == NULL || opts[1] == NULL || opts[2] == NULL ||
(atoi(opts[1]) == 128 && opts[3] == NULL))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Syntax error", name, line);
return 1;
}
tone->name = safe_strdup(opts[0]);
tone->instype = 1;
if (atoi(opts[1]) == 128) /* drum */
{
tone->font_bank = 128;
tone->font_preset = atoi(opts[2]);
tone->font_keynote = atoi(opts[3]);
opts += 4;
}
else
{
tone->font_bank = atoi(opts[1]);
tone->font_preset = atoi(opts[2]);
if (opts[3] && isdigit(opts[3][0]))
{
tone->font_keynote = atoi(opts[3]);
opts += 4;
}
else
{
tone->font_keynote = -1;
opts += 3;
}
}
}
else if (strcmp(pat, "%sample") == 0) /* Sample extention */
{
/* %sample filename */
if (opts[0] == NULL)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Syntax error", name, line);
return 1;
}
tone->name = safe_strdup(opts[0]);
tone->instype = 2;
opts++;
}
else
{
tone->instype = 0;
tone->name = safe_strdup(pat);
}
for (j = 0; opts[j] != NULL; j++)
{
int err;
if ((err = set_gus_patchconf_opts(name, line, opts[j], tone)) != 0)
return err;
}
if (tone->comment == NULL)
tone->comment = safe_strdup(tone->name);
return 0;
}
int Instruments::set_patchconf(const char *name, int line, ToneBank *bank, char *w[], int dr, int mapid, int bankmapfrom, int bankno)
{
int i;
i = atoi(w[0]);
if (!dr)
i -= progbase;
if (i < 0 || i > 127)
{
if (dr)
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Drum number must be between "
"0 and 127",
name, line);
else
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Program must be between "
"%d and %d",
name, line, progbase, 127 + progbase);
return 1;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
return 1;
}
if (set_gus_patchconf(name, line, &bank->tone[i], w[1], w + 2))
return 1;
if (mapid != INST_NO_MAP)
set_instrument_map(mapid, bankmapfrom, i, bankno, i);
return 0;
}
/* string[0] should not be '#' */
int Instruments::strip_trailing_comment(char *string, int next_token_index)
{
if (string[next_token_index - 1] == '#' /* strip \1 in /^\S+(#*[ \t].*)/ */
&& (string[next_token_index] == ' ' || string[next_token_index] == '\t'))
{
string[next_token_index] = '\0'; /* new c-string terminator */
while (string[--next_token_index - 1] == '#')
;
}
return next_token_index;
}
char *Instruments::expand_variables(char *string, MBlockList *varbuf, const char *basedir)
{
char *p, *expstr;
const char *copystr;
int limlen, copylen, explen, varlen, braced;
if ((p = strchr(string, '$')) == NULL)
return string;
varlen = (int)strlen(basedir);
explen = limlen = 0;
expstr = NULL;
copystr = string;
copylen = p - string;
string = p;
for (;;)
{
if (explen + copylen + 1 > limlen)
{
limlen += copylen + 128;
expstr = (char*)memcpy(new_segment(varbuf, limlen), expstr, explen);
}
memcpy(&expstr[explen], copystr, copylen);
explen += copylen;
if (*string == '\0')
break;
else if (*string == '$')
{
braced = *++string == '{';
if (braced)
{
if ((p = strchr(string + 1, '}')) == NULL)
p = string; /* no closing brace */
else
string++;
}
else
for (p = string; isalnum(*p) || *p == '_'; p++);
if (p == string) /* empty */
{
copystr = "${";
copylen = 1 + braced;
}
else
{
if (p - string == 7 && memcmp(string, "basedir", 7) == 0)
{
copystr = basedir;
copylen = varlen;
}
else /* undefined variable */
copylen = 0;
string = p + braced;
}
}
else /* search next */
{
p = strchr(string, '$');
if (p == NULL)
copylen = (int)strlen(string);
else
copylen = int(p - string);
copystr = string;
string += copylen;
}
}
expstr[explen] = '\0';
return expstr;
}
int Instruments::read_config_file(const char *name, int self, int allow_missing_file)
{
timidity_file *tf;
char buf[1024], *tmp, *w[MAXWORDS + 1], *cp;
ToneBank *bank = NULL;
int i, j, k, line = 0, words, errcnt = 0;
static int rcf_count = 0;
int dr = 0, bankno = 0, mapid = INST_NO_MAP, origbankno = 0x7FFFFFFF;
int extension_flag, param_parse_err;
MBlockList varbuf;
const char *basedir;
char *sep;
if (rcf_count > 50)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"Probable source loop in configuration files");
return READ_CONFIG_RECURSION;
}
tf = open_file(name, sfreader);
if (tf == NULL)
return allow_missing_file ? READ_CONFIG_FILE_NOT_FOUND :
READ_CONFIG_ERROR;
init_mblock(&varbuf);
if (!self)
{
char *c = strdup_mblock(&varbuf, tf->filename.c_str());
basedir = c;
for (; *c; c++) if (*c == '\\') *c = '/';
sep = (char*)strrchr(basedir, '/');
}
else
sep = NULL;
if (sep == NULL)
{
basedir = ".";
}
else
{
if ((cp = (char*)strchr(sep, '#')) != NULL)
sep = cp + 1; /* inclusive of '#' */
*sep = '\0';
}
while (tf_gets(buf, sizeof(buf), tf))
{
line++;
if (strncmp(buf, "#extension", 10) == 0) {
extension_flag = 1;
i = 10;
}
else
{
extension_flag = 0;
i = 0;
}
while (isspace(buf[i])) /* skip /^\s*(?#)/ */
i++;
if (buf[i] == '#' || buf[i] == '\0') /* /^#|^$/ */
continue;
tmp = expand_variables(buf, &varbuf, basedir);
j = (int)strcspn(tmp + i, " \t\r\n\240");
if (j == 0)
j = (int)strlen(tmp + i);
j = strip_trailing_comment(tmp + i, j);
tmp[i + j] = '\0'; /* terminate the first token */
w[0] = tmp + i;
i += j + 1;
words = param_parse_err = 0;
while (words < MAXWORDS - 1) /* -1 : next arg */
{
char *terminator;
while (isspace(tmp[i])) /* skip /^\s*(?#)/ */
i++;
if (tmp[i] == '\0'
|| tmp[i] == '#') /* /\s#/ */
break;
if ((tmp[i] == '"' || tmp[i] == '\'')
&& (terminator = strchr(tmp + i + 1, tmp[i])) != NULL)
{
if (!isspace(terminator[1]) && terminator[1] != '\0')
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: there must be at least one whitespace between "
"string terminator (%c) and the next parameter", name, line, tmp[i]);
CHECKERRLIMIT;
param_parse_err = 1;
break;
}
w[++words] = tmp + i + 1;
i = terminator - tmp + 1;
*terminator = '\0';
}
else /* not terminated */
{
j = (int)strcspn(tmp + i, " \t\r\n\240");
if (j > 0)
j = strip_trailing_comment(tmp + i, j);
w[++words] = tmp + i;
i += j;
if (tmp[i] != '\0') /* unless at the end-of-string (i.e. EOF) */
tmp[i++] = '\0'; /* terminate the token */
}
}
if (param_parse_err)
continue;
w[++words] = NULL;
/*
* #extension [something...]
*/
/* #extension timeout program sec */
if (strcmp(w[0], "timeout") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension timeout "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
bank->tone[i].loop_timeout = atoi(w[2]);
}
/* #extension copydrumset drumset */
else if (strcmp(w[0], "copydrumset") == 0)
{
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No copydrumset number given",
name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension copydrumset "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or "
"drum set before assignment", name, line);
CHECKERRLIMIT;
continue;
}
copybank(bank, drumset[i], mapid, origbankno, bankno);
}
/* #extension copybank bank */
else if (strcmp(w[0], "copybank") == 0)
{
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No copybank number given",
name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension copybank "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or "
"drum set before assignment", name, line);
CHECKERRLIMIT;
continue;
}
copybank(bank, tonebank[i], mapid, origbankno, bankno);
}
/* #extension copymap tomapid frommapid */
else if (strcmp(w[0], "copymap") == 0)
{
int mapto, mapfrom;
int toisdrum, fromisdrum;
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if ((mapto = mapname2id(w[1], &toisdrum)) == -1)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Invalid map name: %s", name, line, w[1]);
CHECKERRLIMIT;
continue;
}
if ((mapfrom = mapname2id(w[2], &fromisdrum)) == -1)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Invalid map name: %s", name, line, w[2]);
CHECKERRLIMIT;
continue;
}
if (toisdrum != fromisdrum)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Map type should be matched", name, line);
CHECKERRLIMIT;
continue;
}
if (copymap(mapto, mapfrom, toisdrum))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No free %s available to map",
name, line, toisdrum ? "drum set" : "tone bank");
CHECKERRLIMIT;
continue;
}
}
/* #extension undef program */
else if (strcmp(w[0], "undef") == 0)
{
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No undef number given",
name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension undef "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or "
"drum set before assignment", name, line);
CHECKERRLIMIT;
continue;
}
free_tone_bank_element(&bank->tone[i]);
}
/* #extension altassign numbers... */
else if (strcmp(w[0], "altassign") == 0)
{
ToneBank *bk;
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before altassign", name, line);
CHECKERRLIMIT;
continue;
}
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No alternate assignment", name, line);
CHECKERRLIMIT;
continue;
}
if (!dr) {
printMessage(CMSG_WARNING, VERB_NORMAL,
"%s: line %d: Warning: Not a drumset altassign"
" (ignored)",
name, line);
continue;
}
bk = drumset[bankno];
bk->alt = add_altassign_string(bk->alt, w + 1, words - 1);
} /* #extension legato [program] [0 or 1] */
else if (strcmp(w[0], "legato") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension legato "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
bank->tone[i].legato = atoi(w[2]);
} /* #extension damper [program] [0 or 1] */
else if (strcmp(w[0], "damper") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension damper "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
bank->tone[i].damper_mode = atoi(w[2]);
} /* #extension rnddelay [program] [0 or 1] */
else if (strcmp(w[0], "rnddelay") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension rnddelay "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
bank->tone[i].rnddelay = atoi(w[2]);
} /* #extension level program tva_level */
else if (strcmp(w[0], "level") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[2]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension level "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
cp = w[1];
do {
if (string_to_7bit_range(cp, &j, &k))
{
while (j <= k)
bank->tone[j++].tva_level = i;
}
cp = strchr(cp, ',');
} while (cp++ != NULL);
} /* #extension reverbsend */
else if (strcmp(w[0], "reverbsend") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[2]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension reverbsend "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
cp = w[1];
do {
if (string_to_7bit_range(cp, &j, &k))
{
while (j <= k)
bank->tone[j++].reverb_send = i;
}
cp = strchr(cp, ',');
} while (cp++ != NULL);
} /* #extension chorussend */
else if (strcmp(w[0], "chorussend") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[2]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension chorussend "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
cp = w[1];
do {
if (string_to_7bit_range(cp, &j, &k))
{
while (j <= k)
bank->tone[j++].chorus_send = i;
}
cp = strchr(cp, ',');
} while (cp++ != NULL);
} /* #extension delaysend */
else if (strcmp(w[0], "delaysend") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[2]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension delaysend "
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
cp = w[1];
do {
if (string_to_7bit_range(cp, &j, &k))
{
while (j <= k)
bank->tone[j++].delay_send = i;
}
cp = strchr(cp, ',');
} while (cp++ != NULL);
} /* #extension playnote */
else if (strcmp(w[0], "playnote") == 0)
{
if (words != 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (!bank)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify tone bank or drum set "
"before assignment", name, line);
CHECKERRLIMIT;
continue;
}
i = atoi(w[2]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: extension playnote"
"must be between 0 and 127", name, line);
CHECKERRLIMIT;
continue;
}
cp = w[1];
do {
if (string_to_7bit_range(cp, &j, &k))
{
while (j <= k)
bank->tone[j++].play_note = i;
}
cp = strchr(cp, ',');
} while (cp++ != NULL);
}
else if (!strcmp(w[0], "soundfont"))
{
int order, cutoff, isremove, reso, amp;
char *sf_file;
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No soundfont file given",
name, line);
CHECKERRLIMIT;
continue;
}
sf_file = w[1];
order = cutoff = reso = amp = -1;
isremove = 0;
for (j = 2; j < words; j++)
{
if (strcmp(w[j], "remove") == 0)
{
isremove = 1;
break;
}
if (!(cp = strchr(w[j], '=')))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: bad patch option %s",
name, line, w[j]);
CHECKERRLIMIT;
break;
}
*cp++ = 0;
k = atoi(cp);
if (!strcmp(w[j], "order"))
{
if (k < 0 || (*cp < '0' || *cp > '9'))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: order must be a digit",
name, line);
CHECKERRLIMIT;
break;
}
order = k;
}
else if (!strcmp(w[j], "cutoff"))
{
if (k < 0 || (*cp < '0' || *cp > '9'))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: cutoff must be a digit",
name, line);
CHECKERRLIMIT;
break;
}
cutoff = k;
}
else if (!strcmp(w[j], "reso"))
{
if (k < 0 || (*cp < '0' || *cp > '9'))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: reso must be a digit",
name, line);
CHECKERRLIMIT;
break;
}
reso = k;
}
else if (!strcmp(w[j], "amp"))
{
amp = k;
}
}
if (isremove)
remove_soundfont(sf_file);
else
add_soundfont(sf_file, order, cutoff, reso, amp);
}
else if (!strcmp(w[0], "font"))
{
int bank, preset, keynote;
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: no font command", name, line);
CHECKERRLIMIT;
continue;
}
if (!strcmp(w[1], "exclude"))
{
if (words < 3)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No bank/preset/key is given",
name, line);
CHECKERRLIMIT;
continue;
}
bank = atoi(w[2]);
if (words >= 4)
preset = atoi(w[3]) - progbase;
else
preset = -1;
if (words >= 5)
keynote = atoi(w[4]);
else
keynote = -1;
if (exclude_soundfont(bank, preset, keynote))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No soundfont is given",
name, line);
CHECKERRLIMIT;
}
}
else if (!strcmp(w[1], "order"))
{
int order;
if (words < 4)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No order/bank is given",
name, line);
CHECKERRLIMIT;
continue;
}
order = atoi(w[2]);
bank = atoi(w[3]);
if (words >= 5)
preset = atoi(w[4]) - progbase;
else
preset = -1;
if (words >= 6)
keynote = atoi(w[5]);
else
keynote = -1;
if (order_soundfont(bank, preset, keynote, order))
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No soundfont is given",
name, line);
CHECKERRLIMIT;
}
}
}
else if (!strcmp(w[0], "progbase"))
{
if (words < 2 || *w[1] < '0' || *w[1] > '9')
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
progbase = atoi(w[1]);
}
else if (!strcmp(w[0], "map")) /* map <name> set1 elem1 set2 elem2 */
{
int arg[5], isdrum;
if (words != 6)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if ((arg[0] = mapname2id(w[1], &isdrum)) == -1)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Invalid map name: %s", name, line, w[1]);
CHECKERRLIMIT;
continue;
}
for (i = 2; i < 6; i++)
arg[i - 1] = atoi(w[i]);
if (isdrum)
{
arg[1] -= progbase;
arg[3] -= progbase;
}
else
{
arg[2] -= progbase;
arg[4] -= progbase;
}
for (i = 1; i < 5; i++)
if (arg[i] < 0 || arg[i] > 127)
break;
if (i != 5)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Invalid parameter", name, line);
CHECKERRLIMIT;
continue;
}
set_instrument_map(arg[0], arg[1], arg[2], arg[3], arg[4]);
}
/*
* Standard configurations
*/
else if (!strcmp(w[0], "dir"))
{
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No directory given", name, line);
CHECKERRLIMIT;
continue;
}
for (i = 1; i < words; i++)
sfreader->add_search_path(w[i]);
}
else if (!strcmp(w[0], "source") || !strcmp(w[0], "trysource"))
{
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No file name given", name, line);
CHECKERRLIMIT;
continue;
}
for (i = 1; i < words; i++)
{
int status;
rcf_count++;
status = read_config_file(w[i], 0, !strcmp(w[0], "trysource"));
rcf_count--;
switch (status) {
case READ_CONFIG_SUCCESS:
break;
case READ_CONFIG_ERROR:
CHECKERRLIMIT;
continue;
case READ_CONFIG_RECURSION:
reuse_mblock(&varbuf);
tf_close(tf);
return READ_CONFIG_RECURSION;
case READ_CONFIG_FILE_NOT_FOUND:
break;
}
}
}
else if (!strcmp(w[0], "default"))
{
if (words != 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Must specify exactly one patch name",
name, line);
CHECKERRLIMIT;
continue;
}
strncpy(def_instr_name, w[1], 255);
def_instr_name[255] = '\0';
default_instrument_name = def_instr_name;
}
/* drumset [mapid] num */
else if (!strcmp(w[0], "drumset"))
{
int newmapid, isdrum, newbankno;
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No drum set number given", name, line);
CHECKERRLIMIT;
continue;
}
if (words != 2 && !isdigit(*w[1]))
{
if ((newmapid = mapname2id(w[1], &isdrum)) == -1 || !isdrum)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Invalid drum set map name: %s", name, line, w[1]);
CHECKERRLIMIT;
continue;
}
words--;
memmove(&w[1], &w[2], sizeof w[0] * words);
}
else
newmapid = INST_NO_MAP;
i = atoi(w[1]) - progbase;
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Drum set must be between %d and %d",
name, line,
progbase, progbase + 127);
CHECKERRLIMIT;
continue;
}
newbankno = i;
i = alloc_instrument_map_bank(1, newmapid, i);
if (i == -1)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No free drum set available to map",
name, line);
CHECKERRLIMIT;
continue;
}
if (words == 2)
{
bank = drumset[i];
bankno = i;
mapid = newmapid;
origbankno = newbankno;
dr = 1;
}
else
{
if (words < 4 || *w[2] < '0' || *w[2] > '9')
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (set_patchconf(name, line, drumset[i], &w[2], 1, newmapid, newbankno, i))
{
CHECKERRLIMIT;
continue;
}
}
}
/* bank [mapid] num */
else if (!strcmp(w[0], "bank"))
{
int newmapid, isdrum, newbankno;
if (words < 2)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No bank number given", name, line);
CHECKERRLIMIT;
continue;
}
if (words != 2 && !isdigit(*w[1]))
{
if ((newmapid = mapname2id(w[1], &isdrum)) == -1 || isdrum)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Invalid bank map name: %s", name, line, w[1]);
CHECKERRLIMIT;
continue;
}
words--;
memmove(&w[1], &w[2], sizeof w[0] * words);
}
else
newmapid = INST_NO_MAP;
i = atoi(w[1]);
if (i < 0 || i > 127)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: Tone bank must be between 0 and 127",
name, line);
CHECKERRLIMIT;
continue;
}
newbankno = i;
i = alloc_instrument_map_bank(0, newmapid, i);
if (i == -1)
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: No free tone bank available to map",
name, line);
CHECKERRLIMIT;
continue;
}
if (words == 2)
{
bank = tonebank[i];
bankno = i;
mapid = newmapid;
origbankno = newbankno;
dr = 0;
}
else
{
if (words < 4 || *w[2] < '0' || *w[2] > '9')
{
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (set_patchconf(name, line, tonebank[i], &w[2], 0, newmapid, newbankno, i))
{
CHECKERRLIMIT;
continue;
}
}
}
else
{
if (words < 2 || *w[0] < '0' || *w[0] > '9')
{
if (extension_flag)
continue;
printMessage(CMSG_ERROR, VERB_NORMAL,
"%s: line %d: syntax error", name, line);
CHECKERRLIMIT;
continue;
}
if (set_patchconf(name, line, bank, w, dr, mapid, origbankno, bankno))
{
CHECKERRLIMIT;
continue;
}
}
}
reuse_mblock(&varbuf);
tf_close(tf);
return (errcnt == 0) ? READ_CONFIG_SUCCESS : READ_CONFIG_ERROR;
}
}