mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-27 20:20:40 +00:00
1612 lines
38 KiB
C++
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;
|
|
}
|
|
|
|
}
|