vkdoom_m/src/timidity/timidity.cpp
Randy Heit f6866df0e6 - Made the maximum number of TiMidity voices configurable through the
timidity_voices cvar.
- Added stats lines for the OPL and Timidity MIDI devices.
- Completely changed the way TiMidity volume calculations are done. It
  should now be extremely close to the output a real GUS would produce with
  its official MIDI player (excepting where TiMidity normalizes sample
  volumes). The new equations more closely match what is specified by the DLS
  and SF2 specs (but not quite), so I presume it's also more musically
  correct than what TiMidity (and TiMidity++) do.


SVN r925 (trunk)
2008-04-19 05:57:09 +00:00

564 lines
14 KiB
C++

/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
timidity.c
*/
#include <stdio.h>
#include <stdlib.h>
#include "timidity.h"
#include "templates.h"
#include "m_alloc.h"
#include "cmdlib.h"
#include "c_cvars.h"
#include "i_system.h"
#include "files.h"
CVAR(String, timidity_config, CONFIG_FILE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(Int, timidity_voices, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
namespace Timidity
{
ToneBank *tonebank[MAXBANK], *drumset[MAXBANK];
static FString def_instr_name;
int openmode = OM_FILEORLUMP;
#define MAXWORDS 10
static int read_config_file(const char *name, bool ismain)
{
FileReader *fp;
char tmp[1024], *w[MAXWORDS], *cp;
ToneBank *bank = NULL;
int i, j, k, line = 0, words;
static int rcf_count = 0;
int lumpnum;
if (rcf_count > 50)
{
Printf("Timidity: Probable source loop in configuration files\n");
return (-1);
}
if (ismain) openmode = OM_FILEORLUMP;
if (!(fp = open_filereader(name, openmode, &lumpnum)))
return -1;
if (ismain)
{
if (lumpnum > 0)
{
openmode = OM_LUMP;
clear_pathlist(); // when reading from a PK3 we won't want to use any external path
}
else
{
openmode = OM_FILE;
}
}
while (fp->Gets(tmp, sizeof(tmp)))
{
line++;
w[words = 0] = strtok(tmp, " \t\r\n\240");
if (!w[0]) continue;
/* Originally the TiMidity++ extensions were prefixed like this */
if (strcmp(w[0], "#extension") == 0)
words = -1;
else if (*w[0] == '#')
continue;
while (w[words] && *w[words] != '#' && (words < MAXWORDS))
w[++words] = strtok(0, " \t\r\n\240");
/*
* TiMidity++ adds a number of extensions to the config file format.
* Many of them are completely irrelevant to SDL_sound, but at least
* we shouldn't choke on them.
*
* Unfortunately the documentation for these extensions is often quite
* vague, gramatically strange or completely absent.
*/
if (
!strcmp(w[0], "comm") /* "comm" program second */
|| !strcmp(w[0], "HTTPproxy") /* "HTTPproxy" hostname:port */
|| !strcmp(w[0], "FTPproxy") /* "FTPproxy" hostname:port */
|| !strcmp(w[0], "mailaddr") /* "mailaddr" your-mail-address */
|| !strcmp(w[0], "opt") /* "opt" timidity-options */
)
{
/*
* + "comm" sets some kind of comment -- the documentation is too
* vague for me to understand at this time.
* + "HTTPproxy", "FTPproxy" and "mailaddr" are for reading data
* over a network, rather than from the file system.
* + "opt" specifies default options for TiMidity++.
*
* These are all quite useless for our version of TiMidity, so
* they can safely remain no-ops.
*/
}
else if (!strcmp(w[0], "timeout")) /* "timeout" program second */
{
/*
* Specifies a timeout value of the program. A number of seconds
* 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");
}
else if (!strcmp(w[0], "copydrumset") /* "copydrumset" drumset */
|| !strcmp(w[0], "copybank")) /* "copybank" bank */
{
/*
* Copies all the settings of the specified drumset or bank to
* the current drumset or bank. May be useful later, but not a
* high priority.
*/
Printf("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]);
}
else if (!strcmp(w[0], "undef")) /* "undef" progno */
{
/*
* Undefines the tone "progno" of the current tone bank (or
* drum set?). Not a high priority.
*/
Printf("FIXME: Implement \"undef\" in TiMidity config.\n");
}
else if (!strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */
{
/*
* Sets the alternate assign for drum set. Whatever that's
* supposed to mean.
*/
Printf("FIXME: Implement \"altassign\" in TiMidity config.\n");
}
else if (!strcmp(w[0], "soundfont") || !strcmp(w[0], "font"))
{
/*
* I can't find any documentation for these, but I guess they're
* an alternative way of loading/unloading instruments.
*
* "soundfont" sf_file "remove"
* "soundfont" sf_file ["order=" order] ["cutoff=" cutoff]
* ["reso=" reso] ["amp=" amp]
* "font" "exclude" bank preset keynote
* "font" "order" order bank preset keynote
*/
Printf("FIXME: Implmement \"%s\" in TiMidity config.\n", w[0]);
}
else if (!strcmp(w[0], "progbase"))
{
/*
* The documentation for this makes absolutely no sense to me, but
* 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");
}
else if (!strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */
{
/*
* This extension is the one we will need to implement, as it is
* used by the "eawpats". Unfortunately I cannot find any
* 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");
}
/* Standard TiMidity config */
else if (!strcmp(w[0], "dir"))
{
if (words < 2)
{
Printf("%s: line %d: No directory given\n", name, line);
delete fp;
return -2;
}
for (i = 1; i < words; i++)
add_to_pathlist(w[i]);
}
else if (!strcmp(w[0], "source"))
{
if (words < 2)
{
Printf("%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);
rcf_count--;
}
}
else if (!strcmp(w[0], "default"))
{
if (words != 2)
{
Printf("%s: line %d: Must specify exactly one patch name\n", name, line);
delete fp;
return -2;
}
def_instr_name = w[1];
}
else if (!strcmp(w[0], "drumset"))
{
if (words < 2)
{
Printf("%s: line %d: No drum set number given\n", name, line);
delete fp;
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);
delete fp;
return -2;
}
if (drumset[i] == NULL)
{
drumset[i] = new ToneBank;
}
bank = drumset[i];
}
else if (!strcmp(w[0], "bank"))
{
if (words < 2)
{
Printf("%s: line %d: No bank number given\n", name, line);
delete fp;
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);
delete fp;
return -2;
}
if (tonebank[i] == NULL)
{
tonebank[i] = new ToneBank;
}
bank = tonebank[i];
}
else
{
if ((words < 2) || (*w[0] < '0' || *w[0] > '9'))
{
Printf("%s: line %d: syntax error\n", name, line);
delete fp;
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);
delete fp;
return -2;
}
if (bank == NULL)
{
Printf("%s: line %d: Must specify tone bank or drum set before assignment\n", name, line);
delete fp;
return -2;
}
bank->tone[i].name = w[1];
bank->tone[i].note = bank->tone[i].amp = bank->tone[i].pan =
bank->tone[i].strip_loop = bank->tone[i].strip_envelope =
bank->tone[i].strip_tail = -1;
for (j = 2; j<words; j++)
{
if (!(cp=strchr(w[j], '=')))
{
Printf("%s: line %d: bad patch option %s\n", name, line, w[j]);
delete fp;
return -2;
}
*cp++ = 0;
if (!strcmp(w[j], "amp"))
{
k = atoi(cp);
if ((k < 0 || k > MAX_AMPLIFICATION) || (*cp < '0' || *cp > '9'))
{
Printf("%s: line %d: amplification must be between 0 and %d\n", name, line, MAX_AMPLIFICATION);
delete fp;
return -2;
}
bank->tone[i].amp = k;
}
else if (!strcmp(w[j], "note"))
{
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);
delete fp;
return -2;
}
bank->tone[i].note = k;
}
else if (!strcmp(w[j], "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')))
{
Printf("%s: line %d: panning must be left, right, "
"center, or between -100 and 100\n", name, line);
delete fp;
return -2;
}
bank->tone[i].pan = k;
}
else if (!strcmp(w[j], "keep"))
{
if (!strcmp(cp, "env"))
bank->tone[i].strip_envelope = 0;
else if (!strcmp(cp, "loop"))
bank->tone[i].strip_loop = 0;
else
{
Printf("%s: line %d: keep must be env or loop\n", name, line);
delete fp;
return -2;
}
}
else if (!strcmp(w[j], "strip"))
{
if (!strcmp(cp, "env"))
bank->tone[i].strip_envelope = 1;
else if (!strcmp(cp, "loop"))
bank->tone[i].strip_loop = 1;
else if (!strcmp(cp, "tail"))
bank->tone[i].strip_tail = 1;
else
{
Printf("%s: line %d: strip must be env, loop, or tail\n", name, line);
delete fp;
return -2;
}
}
else
{
Printf("%s: line %d: bad patch option %s\n", name, line, w[j]);
delete fp;
return -2;
}
}
}
}
/*
if (ferror(fp))
{
Printf("Can't read %s: %s\n", name, strerror(errno));
close_file(fp);
return -2;
}
*/
delete fp;
return 0;
}
void FreeAll()
{
free_instruments();
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;
}
}
}
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.
*/
clear_pathlist();
#ifdef _WIN32
add_to_pathlist("C:\\TIMIDITY");
add_to_pathlist("\\TIMIDITY");
add_to_pathlist(progdir);
#else
add_to_pathlist("/usr/local/lib/timidity");
add_to_pathlist("/etc/timidity");
add_to_pathlist("/etc");
#endif
/* 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()
{
return LoadConfig(timidity_config);
}
Renderer::Renderer(float sample_rate)
{
rate = sample_rate;
patches = NULL;
resample_buffer_size = 0;
resample_buffer = NULL;
voice = NULL;
adjust_panning_immediately = false;
control_ratio = clamp(int(rate / CONTROLS_PER_SECOND), 1, MAX_CONTROL_RATIO);
lost_notes = 0;
cut_notes = 0;
default_instrument = NULL;
default_program = DEFAULT_PROGRAM;
if (def_instr_name.IsNotEmpty())
set_default_instrument(def_instr_name);
voices = clamp<int>(timidity_voices, 16, 256);
voice = new Voice[voices];
drumchannels = DEFAULT_DRUMCHANNELS;
}
Renderer::~Renderer()
{
if (resample_buffer != NULL)
{
M_Free(resample_buffer);
}
if (voice != NULL)
{
delete[] voice;
}
}
void Renderer::ComputeOutput(float *buffer, int count)
{
// count is in samples, not bytes.
if (count <= 0)
{
return;
}
Voice *v = &voice[0];
memset(buffer, 0, sizeof(float)*count*2); // An integer 0 is also a float 0.
if (resample_buffer_size < count)
{
resample_buffer_size = count;
resample_buffer = (sample_t *)M_Realloc(resample_buffer, count * sizeof(float) * 2);
}
for (int i = 0; i < voices; i++, v++)
{
if (v->status != VOICE_FREE)
{
mix_voice(this, buffer, v, count);
}
}
}
void Renderer::MarkInstrument(int banknum, int percussion, int instr)
{
ToneBank *bank;
if (banknum >= MAXBANK)
{
return;
}
if (banknum != 0)
{
/* Mark the standard bank in case it's not defined by this one. */
MarkInstrument(0, percussion, instr);
}
if (percussion)
{
bank = drumset[banknum];
}
else
{
bank = tonebank[banknum];
}
if (bank == NULL)
{
return;
}
if (bank->instrument[instr] == NULL)
{
bank->instrument[instr] = MAGIC_LOAD_INSTRUMENT;
}
}
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
void cmsg(int type, int verbosity_level, const char *fmt, ...)
{
/*
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);
vsprintf(buf, fmt, args);
va_end(args);
OutputDebugString(buf);
#endif
}
}