- cleanup of the TimidityMIDIDevice(GUS) backend code to eliminate the storage in global variables and to remove the dependencies on core ZDoom code.

The organization here is now the same as for the Timidity++ device, i.e. it is the device owning the instruments to give better control over their lifecycle.
This commit is contained in:
Christoph Oelckers 2019-09-24 23:08:56 +02:00
commit d557f609cf
24 changed files with 1547 additions and 916 deletions

View file

@ -37,54 +37,158 @@
#include "v_text.h"
#include "timidity.h"
#include "i_soundfont.h"
#include "timidity_file.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
CUSTOM_CVAR(String, midi_config, "gzdoom", CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
Timidity::FreeAll();
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_GUS)
{
MIDIDeviceChanged(-1, true);
}
}
CVAR(Int, midi_voices, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(String, gus_patchdir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CUSTOM_CVAR(Bool, midi_dmxgus, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // This was 'true' but since it requires special setup that's not such a good idea.
{
Timidity::FreeAll();
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_GUS)
{
MIDIDeviceChanged(-1, true);
}
}
CVAR(Int, gus_memsize, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
namespace Timidity
{
ToneBank *tonebank[MAXBANK], *drumset[MAXBANK];
static std::string def_instr_name;
std::unique_ptr<FSoundFontReader> gus_sfreader;
static bool InitReader(const char *config_file)
static long ParseCommandLine(const char* args, int* argc, char** argv)
{
auto reader = sfmanager.OpenSoundFont(config_file, SF_GUS|SF_SF2);
if (reader == nullptr)
int count;
char* buffplace;
count = 0;
buffplace = NULL;
if (argv != NULL)
{
Printf(TEXTCOLOR_RED "%s: Unable to load sound font\n", config_file);
return false; // No sound font could be opened.
buffplace = argv[0];
}
gus_sfreader.reset(reader);
//config_name = config_file;
return true;
for (;;)
{
while (*args <= ' ' && *args)
{ // skip white space
args++;
}
if (*args == 0)
{
break;
}
else if (*args == '\"')
{ // read quoted string
char stuff;
if (argv != NULL)
{
argv[count] = buffplace;
}
count++;
args++;
do
{
stuff = *args++;
if (stuff == '\"')
{
stuff = 0;
}
else if (stuff == 0)
{
args--;
}
if (argv != NULL)
{
*buffplace = stuff;
}
buffplace++;
} while (stuff);
}
else
{ // read unquoted string
const char* start = args++, * end;
while (*args && *args > ' ' && *args != '\"')
args++;
end = args;
if (argv != NULL)
{
argv[count] = buffplace;
while (start < end)
* buffplace++ = *start++;
*buffplace++ = 0;
}
else
{
buffplace += end - start + 1;
}
count++;
}
}
if (argc != NULL)
{
*argc = count;
}
return (long)(buffplace - (char*)0);
}
class FCommandLine
{
public:
FCommandLine(const char* commandline)
{
cmd = commandline;
_argc = -1;
_argv = NULL;
argsize = 0;
}
static int read_config_file(const char *name, bool ismain)
~FCommandLine()
{
if (_argv != NULL)
{
delete[] _argv;
}
}
int argc()
{
if (_argc == -1)
{
argsize = ParseCommandLine(cmd, &_argc, NULL);
}
return _argc;
}
char* operator[] (int i)
{
if (_argv == NULL)
{
int count = argc();
_argv = new char* [count + (argsize + sizeof(char*) - 1) / sizeof(char*)];
_argv[0] = (char*)_argv + count * sizeof(char*);
ParseCommandLine(cmd, NULL, _argv);
}
return _argv[i];
}
const char* args() { return cmd; }
void Shift()
{
// Only valid after _argv has been filled.
for (int i = 1; i < _argc; ++i)
{
_argv[i - 1] = _argv[i];
}
}
private:
const char* cmd;
int _argc;
char** _argv;
long argsize;
};
int Instruments::read_config_file(const char *name)
{
FileReader fp;
char tmp[1024], *cp;
ToneBank *bank = NULL;
int i, j, k, line = 0, words;
@ -92,28 +196,20 @@ static int read_config_file(const char *name, bool ismain)
if (rcf_count > 50)
{
Printf("Timidity: Probable source loop in configuration files\n");
cmsg(CMSG_ERROR, VERB_NORMAL, "Timidity: Probable source loop in configuration files\n");
return (-1);
}
if (ismain)
{
if (!InitReader(name)) return -1;
fp = gus_sfreader->OpenMainConfigFile();
FreeAll();
}
else
{
fp = gus_sfreader->LookupFile(name).first;
}
if (!fp .isOpen())
auto fp = sfreader->open_timidity_file(name);
if (!fp)
{
cmsg(CMSG_ERROR, VERB_NORMAL, "Timidity: Unable to open config file\n");
return -1;
}
while (fp.Gets(tmp, sizeof(tmp)))
while (fp->gets(tmp, sizeof(tmp)))
{
line++;
FCommandLine w(tmp, true);
FCommandLine w(tmp);
words = w.argc();
if (words == 0) continue;
@ -171,7 +267,7 @@ static int read_config_file(const char *name, bool ismain)
* before TiMidity kills the note. This may be useful to implement
* later, but I don't see any urgent need for it.
*/
//Printf("FIXME: Implement \"timeout\" in TiMidity config.\n");
//cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"timeout\" in TiMidity config.\n");
}
else if (!strcmp(w[0], "copydrumset") /* "copydrumset" drumset */
|| !strcmp(w[0], "copybank")) /* "copybank" bank */
@ -181,7 +277,7 @@ static int read_config_file(const char *name, bool ismain)
* the current drumset or bank. May be useful later, but not a
* high priority.
*/
//Printf("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]);
//cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"%s\" in TiMidity config.\n", w[0]);
}
else if (!strcmp(w[0], "undef")) /* "undef" progno */
{
@ -189,7 +285,7 @@ static int read_config_file(const char *name, bool ismain)
* Undefines the tone "progno" of the current tone bank (or
* drum set?). Not a high priority.
*/
//Printf("FIXME: Implement \"undef\" in TiMidity config.\n");
//cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"undef\" in TiMidity config.\n");
}
else if (!strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */
{
@ -197,7 +293,7 @@ static int read_config_file(const char *name, bool ismain)
* Sets the alternate assign for drum set. Whatever that's
* supposed to mean.
*/
//Printf("FIXME: Implement \"altassign\" in TiMidity config.\n");
//cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"altassign\" in TiMidity config.\n");
}
else if (!strcmp(w[0], "soundfont"))
{
@ -208,7 +304,7 @@ static int read_config_file(const char *name, bool ismain)
*/
if (words < 2)
{
Printf("%s: line %d: No soundfont given\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No soundfont given\n", name, line);
return -2;
}
if (words > 2 && !strcmp(w[2], "remove"))
@ -223,7 +319,7 @@ static int read_config_file(const char *name, bool ismain)
{
if (!(cp = strchr(w[i], '=')))
{
Printf("%s: line %d: bad soundfont option %s\n", name, line, w[i]);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad soundfont option %s\n", name, line, w[i]);
return -2;
}
}
@ -240,7 +336,7 @@ static int read_config_file(const char *name, bool ismain)
if (words < 3)
{
Printf("%s: line %d: syntax error\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error\n", name, line);
return -2;
}
@ -256,7 +352,7 @@ static int read_config_file(const char *name, bool ismain)
}
else
{
Printf("%s: line %d: font subcommand must be 'order' or 'exclude'\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: font subcommand must be 'order' or 'exclude'\n", name, line);
return -2;
}
if (i < words)
@ -290,7 +386,7 @@ static int read_config_file(const char *name, bool ismain)
* apparently it sets some sort of base offset for tone numbers.
* Why anyone would want to do this is beyond me.
*/
//Printf("FIXME: Implement \"progbase\" in TiMidity config.\n");
//cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"progbase\" in TiMidity config.\n");
}
else if (!strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */
{
@ -300,7 +396,7 @@ static int read_config_file(const char *name, bool ismain)
* documentation whatsoever for it, but it looks like it's used
* for remapping one instrument to another somehow.
*/
//Printf("FIXME: Implement \"map\" in TiMidity config.\n");
//cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"map\" in TiMidity config.\n");
}
/* Standard TiMidity config */
@ -309,26 +405,26 @@ static int read_config_file(const char *name, bool ismain)
{
if (words < 2)
{
Printf("%s: line %d: No directory given\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No directory given\n", name, line);
return -2;
}
for (i = 1; i < words; i++)
{
// Q: How does this deal with relative paths? In this form it just does not work.
gus_sfreader->AddPath(w[i]);
sfreader->timidity_add_path(w[i]);
}
}
else if (!strcmp(w[0], "source"))
{
if (words < 2)
{
Printf("%s: line %d: No file name given\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No file name given\n", name, line);
return -2;
}
for (i=1; i<words; i++)
{
rcf_count++;
read_config_file(w[i], false);
read_config_file(w[i]);
rcf_count--;
}
}
@ -336,7 +432,7 @@ static int read_config_file(const char *name, bool ismain)
{
if (words != 2)
{
Printf("%s: line %d: Must specify exactly one patch name\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: Must specify exactly one patch name\n", name, line);
return -2;
}
def_instr_name = w[1];
@ -345,13 +441,13 @@ static int read_config_file(const char *name, bool ismain)
{
if (words < 2)
{
Printf("%s: line %d: No drum set number given\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No drum set number given\n", name, line);
return -2;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
Printf("%s: line %d: Drum set must be between 0 and 127\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: Drum set must be between 0 and 127\n", name, line);
return -2;
}
if (drumset[i] == NULL)
@ -364,13 +460,13 @@ static int read_config_file(const char *name, bool ismain)
{
if (words < 2)
{
Printf("%s: line %d: No bank number given\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No bank number given\n", name, line);
return -2;
}
i = atoi(w[1]);
if (i < 0 || i > 127)
{
Printf("%s: line %d: Tone bank must be between 0 and 127\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: Tone bank must be between 0 and 127\n", name, line);
return -2;
}
if (tonebank[i] == NULL)
@ -383,18 +479,18 @@ static int read_config_file(const char *name, bool ismain)
{
if ((words < 2) || (*w[0] < '0' || *w[0] > '9'))
{
Printf("%s: line %d: syntax error\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error\n", name, line);
return -2;
}
i = atoi(w[0]);
if (i < 0 || i > 127)
{
Printf("%s: line %d: Program must be between 0 and 127\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: Program must be between 0 and 127\n", name, line);
return -2;
}
if (bank == NULL)
{
Printf("%s: line %d: Must specify tone bank or drum set before assignment\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: Must specify tone bank or drum set before assignment\n", name, line);
return -2;
}
bank->tone[i].note = bank->tone[i].pan =
@ -428,7 +524,7 @@ static int read_config_file(const char *name, bool ismain)
{
if (!(cp=strchr(w[j], '=')))
{
Printf("%s: line %d: bad patch option %s\n", name, line, w[j]);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad patch option %s\n", name, line, w[j]);
return -2;
}
*cp++ = 0;
@ -441,7 +537,7 @@ static int read_config_file(const char *name, bool ismain)
k = atoi(cp);
if ((k < 0 || k > 127) || (*cp < '0' || *cp > '9'))
{
Printf("%s: line %d: note must be between 0 and 127\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: note must be between 0 and 127\n", name, line);
return -2;
}
bank->tone[i].note = k;
@ -459,7 +555,7 @@ static int read_config_file(const char *name, bool ismain)
if ((k < 0 || k > 127) ||
(k == 0 && *cp != '-' && (*cp < '0' || *cp > '9')))
{
Printf("%s: line %d: panning must be left, right, "
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: panning must be left, right, "
"center, or between -100 and 100\n", name, line);
return -2;
}
@ -473,7 +569,7 @@ static int read_config_file(const char *name, bool ismain)
bank->tone[i].strip_loop = 0;
else
{
Printf("%s: line %d: keep must be env or loop\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: keep must be env or loop\n", name, line);
return -2;
}
}
@ -487,13 +583,13 @@ static int read_config_file(const char *name, bool ismain)
bank->tone[i].strip_tail = 1;
else
{
Printf("%s: line %d: strip must be env, loop, or tail\n", name, line);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: strip must be env, loop, or tail\n", name, line);
return -2;
}
}
else
{
Printf("%s: line %d: bad patch option %s\n", name, line, w[j]);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad patch option %s\n", name, line, w[j]);
return -2;
}
}
@ -502,92 +598,13 @@ static int read_config_file(const char *name, bool ismain)
return 0;
}
void FreeAll()
// When loading DMXGUS the sfreader's default file must be the DMXGUS file, not the config as for patch sets.
int Instruments::LoadDMXGUS(int gus_memsize)
{
free_instruments();
font_freeall();
for (int i = 0; i < MAXBANK; ++i)
{
if (tonebank[i] != NULL)
{
delete tonebank[i];
tonebank[i] = NULL;
}
if (drumset[i] != NULL)
{
delete drumset[i];
drumset[i] = NULL;
}
}
}
static FString currentConfig;
int LoadConfig(const char *filename)
{
/* !!! FIXME: This may be ugly, but slightly less so than requiring the
* default search path to have only one element. I think.
*
* We only need to include the likely locations for the config
* file itself since that file should contain any other directory
* that needs to be added to the search path.
*/
if (currentConfig.CompareNoCase(filename) == 0) return 0;
/* Some functions get aggravated if not even the standard banks are available. */
if (tonebank[0] == NULL)
{
tonebank[0] = new ToneBank;
drumset[0] = new ToneBank;
}
return read_config_file(filename, true);
}
int LoadConfig()
{
if (midi_dmxgus)
{
return LoadDMXGUS();
}
else
{
return LoadConfig(midi_config);
}
}
int LoadDMXGUS()
{
if (currentConfig.CompareNoCase("DMXGUS") == 0) return 0;
int lump = Wads.CheckNumForName("DMXGUS");
if (lump == -1) lump = Wads.CheckNumForName("DMXGUSC");
if (lump == -1) return LoadConfig(midi_config);
auto data = Wads.OpenLumpReader(lump);
if (data.GetLength() == 0) return LoadConfig(midi_config);
// Check if we got some GUS data before using it.
FString ultradir = getenv("ULTRADIR");
if (ultradir.IsEmpty() && *(*gus_patchdir) == 0) return LoadConfig(midi_config);
currentConfig = "DMXGUS";
FreeAll();
auto psreader = new FPatchSetReader;
// The GUS put its patches in %ULTRADIR%/MIDI so we can try that
if (ultradir.IsNotEmpty())
{
ultradir += "/midi";
psreader->AddPath(ultradir);
}
// Load DMXGUS lump and patches from gus_patchdir
if (*(*gus_patchdir) != 0) psreader->AddPath(gus_patchdir);
gus_sfreader.reset(psreader);
char readbuffer[1024];
auto size = data.GetLength();
auto data = sfreader->open_timidity_file(nullptr);
long size = (data->seek(0, SEEK_END), data->tell());
long read = 0;
uint8_t remap[256];
@ -598,9 +615,9 @@ int LoadDMXGUS()
int status = -1;
int gusbank = (gus_memsize >= 1 && gus_memsize <= 4) ? gus_memsize : -1;
data.Seek(0, FileReader::SeekSet);
data->seek(0, FileReader::SeekSet);
while (data.Gets(readbuffer, 1024) && read < size)
while (data->gets(readbuffer, 1024) && read < size)
{
int i = 0;
while (readbuffer[i] != 0 && i < 1024)
@ -692,32 +709,11 @@ int LoadDMXGUS()
DLS_Data *LoadDLS(FILE *src);
void FreeDLS(DLS_Data *data);
Renderer::Renderer(float sample_rate, const char *args)
Renderer::Renderer(float sample_rate, int voices_, Instruments *inst)
{
int res = 0;
// Load explicitly stated sound font if so desired.
if (args != nullptr && *args != 0)
{
if (!stricmp(args, "DMXGUS")) res = LoadDMXGUS();
res = LoadConfig(args);
}
else if (tonebank[0] == nullptr)
{
res = LoadConfig();
}
if (res < 0)
{
I_Error("Failed to load any MIDI patches");
}
// These can be left empty here if an error occured during sound font initialization.
if (tonebank[0] == NULL)
{
tonebank[0] = new ToneBank;
drumset[0] = new ToneBank;
}
instruments = inst;
rate = sample_rate;
patches = NULL;
@ -736,14 +732,9 @@ Renderer::Renderer(float sample_rate, const char *args)
if (def_instr_name.length() > 0)
set_default_instrument(def_instr_name.c_str());
voices = MAX(*midi_voices, 16);
voices = std::max(voices_, 16);
voice = new Voice[voices];
drumchannels = DEFAULT_DRUMCHANNELS;
#if 0
FILE *f = fopen("c:\\windows\\system32\\drivers\\gm.dls", "rb");
patches = LoadDLS(f);
fclose(f);
#endif
}
Renderer::~Renderer()
@ -801,11 +792,11 @@ void Renderer::MarkInstrument(int banknum, int percussion, int instr)
}
if (percussion)
{
bank = drumset[banknum];
bank = instruments->drumset[banknum];
}
else
{
bank = tonebank[banknum];
bank = instruments->tonebank[banknum];
}
if (bank == NULL)
{
@ -817,22 +808,31 @@ void Renderer::MarkInstrument(int banknum, int percussion, int instr)
}
}
void cmsg(int type, int verbosity_level, const char *fmt, ...)
static void default_cmsg(int type, int verbosity_level, const char* fmt, ...)
{
/*
if (verbosity_level >= VERB_NOISY) return; // Don't waste time on diagnostics.
va_list args;
va_start(args, fmt);
VPrintf(PRINT_HIGH, fmt, args);
msg.VFormat(fmt, args);
*/
#ifdef _WIN32
char buf[1024];
va_list args;
va_start(args, fmt);
myvsnprintf(buf, sizeof buf, fmt, args);
va_end(args);
I_DebugPrint(buf);
#endif
switch (type)
{
case CMSG_ERROR:
vprintf("Error: %s\n", args);
break;
case CMSG_WARNING:
vprintf("Warning: %s\n", args);
break;
case CMSG_INFO:
vprintf("Info: %s\n", args);
break;
}
}
// Allow hosting applications to capture the messages and deal with them themselves.
void (*cmsg)(int type, int verbosity_level, const char* fmt, ...) = default_cmsg;
}