- 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
parent c107657ea8
commit d557f609cf
24 changed files with 1553 additions and 922 deletions

View file

@ -137,7 +137,7 @@ struct timidity_file *open_file(const char *name, SoundFontReaderInterface *sfre
/* This closes files opened with open_file */
void tf_close(struct timidity_file *tf)
{
delete tf;
if (tf) tf->close();
}
/* This is meant for skipping a few bytes. */
@ -157,7 +157,6 @@ void default_ctl_cmsg(int type, int verbosity_level, const char* fmt, ...)
{
if (verbosity_level >= VERB_DEBUG) return; // Don't waste time on diagnostics.
char buffer[2048];
va_list args;
va_start(args, fmt);

View file

@ -34,6 +34,7 @@ struct timidity_file
virtual long read(void* buff, int32_t size, int32_t nitems) = 0;
virtual long seek(long offset, int whence) = 0;
virtual long tell() = 0;
virtual void close() = 0;
};
class SoundFontReaderInterface
@ -76,6 +77,11 @@ struct timidity_file_FILE : public timidity_file
if (!f) return 0;
return ftell(f);
}
void close()
{
if (f) fclose(f);
delete this;
}
};
class BaseSoundFontReader : public SoundFontReaderInterface

View file

@ -37,6 +37,10 @@
#include "i_musicinterns.h"
#include "v_text.h"
#include "timidity/timidity.h"
#include "timidity/playmidi.h"
#include "timidity/instrum.h"
#include "i_soundfont.h"
#include "doomerrors.h"
// MACROS ------------------------------------------------------------------
@ -52,7 +56,78 @@
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// Internal TiMidity MIDI device --------------------------------------------
// CVARS for this device - all of them require a device reset --------------------------------------------
static void CheckRestart()
{
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_GUS)
{
MIDIDeviceChanged(-1, true);
}
}
CUSTOM_CVAR(String, midi_config, "gzdoom", CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
CheckRestart();
}
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.
{
CheckRestart();
}
CUSTOM_CVAR(String, gus_patchdir, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
CheckRestart();
}
CUSTOM_CVAR(Int, midi_voices, 32, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
CheckRestart();
}
CUSTOM_CVAR(Int, gus_memsize, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
CheckRestart();
}
//==========================================================================
//
// Error printing override to redirect to the internal console instead of stdout.
//
//==========================================================================
static void gzdoom_ctl_cmsg(int type, int verbosity_level, const char* fmt, ...)
{
if (verbosity_level >= Timidity::VERB_DEBUG) return; // Don't waste time on diagnostics.
va_list args;
va_start(args, fmt);
FString msg;
msg.VFormat(fmt, args);
va_end(args);
switch (type)
{
case Timidity::CMSG_ERROR:
Printf(TEXTCOLOR_RED "%s\n", msg.GetChars());
break;
case Timidity::CMSG_WARNING:
Printf(TEXTCOLOR_YELLOW "%s\n", msg.GetChars());
break;
case Timidity::CMSG_INFO:
DPrintf(DMSG_SPAMMY, "%s\n", msg.GetChars());
break;
}
}
//==========================================================================
//
// The actual device.
//
//==========================================================================
namespace Timidity { struct Renderer; }
@ -64,7 +139,6 @@ public:
int Open(MidiCallback, void *userdata);
void PrecacheInstruments(const uint16_t *instruments, int count);
FString GetStats();
int GetDeviceType() const override { return MDEV_GUS; }
protected:
@ -73,13 +147,80 @@ protected:
void HandleEvent(int status, int parm1, int parm2);
void HandleLongEvent(const uint8_t *data, int len);
void ComputeOutput(float *buffer, int len);
void LoadConfig(const char *);
};
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// DATA DEFINITIONS -------------------------------------------------
static FString currentConfig;
static Timidity::Instruments* instruments;
// CODE --------------------------------------------------------------------
void TimidityMIDIDevice::LoadConfig(const char *config)
{
if ((midi_dmxgus && *config == 0) || !stricmp(config, "DMXGUS"))
{
if (currentConfig.CompareNoCase("DMXGUS") == 0) return; // aleady loaded
int lump = Wads.CheckNumForName("DMXGUS");
if (lump == -1) lump = Wads.CheckNumForName("DMXGUSC");
if (lump >= 0)
{
auto data = Wads.OpenLumpReader(lump);
if (data.GetLength() > 0)
{
// Check if we got some GUS data before using it.
FString ultradir = getenv("ULTRADIR");
if (ultradir.IsNotEmpty() || *(*gus_patchdir) != 0)
{
auto psreader = new FPatchSetReader(data);
// The GUS put its patches in %ULTRADIR%/MIDI so we can try that
if (ultradir.IsNotEmpty())
{
ultradir += "/midi";
psreader->AddPath(ultradir);
}
if (instruments) delete instruments;
instruments = new Timidity::Instruments(psreader);
// Load DMXGUS lump and patches from gus_patchdir
if (*(*gus_patchdir) != 0) psreader->AddPath(gus_patchdir);
if (instruments->LoadDMXGUS(gus_memsize) < 0)
{
delete instruments;
instruments = nullptr;
I_Error("Unable to initialize instruments for GUS MIDI device");
}
currentConfig = "DMXGUS";
}
}
}
}
if (*config == 0) config = midi_config;
if (currentConfig.CompareNoCase(config) == 0) return;
if (instruments) delete instruments;
instruments = nullptr;
auto reader = sfmanager.OpenSoundFont(config, SF_GUS | SF_SF2);
if (reader == nullptr)
{
I_Error("GUS: %s: Unable to load sound font\n",config);
}
instruments = new Timidity::Instruments(reader);
if (instruments->LoadConfig() < 0)
{
delete instruments;
instruments = nullptr;
I_Error("Unable to initialize instruments for GUS MIDI device");
}
currentConfig = config;
}
//==========================================================================
//
// TimidityMIDIDevice Constructor
@ -89,7 +230,9 @@ protected:
TimidityMIDIDevice::TimidityMIDIDevice(const char *args, int samplerate)
: SoftSynthMIDIDevice(samplerate, 11025, 65535)
{
Renderer = new Timidity::Renderer((float)SampleRate, args);
LoadConfig(args);
Timidity::cmsg = gzdoom_ctl_cmsg;
Renderer = new Timidity::Renderer((float)SampleRate, midi_voices, instruments);
}
//==========================================================================
@ -179,66 +322,6 @@ void TimidityMIDIDevice::ComputeOutput(float *buffer, int len)
for (int i = 0; i < len * 2; i++) buffer[i] *= 0.7f;
}
//==========================================================================
//
// TimidityMIDIDevice :: GetStats
//
//==========================================================================
FString TimidityMIDIDevice::GetStats()
{
FString dots;
FString out;
int i, used;
std::lock_guard<std::mutex> lock(CritSec);
for (i = used = 0; i < Renderer->voices; ++i)
{
int status = Renderer->voice[i].status;
if (!(status & Timidity::VOICE_RUNNING))
{
dots << TEXTCOLOR_PURPLE".";
}
else
{
used++;
if (status & Timidity::VOICE_SUSTAINING)
{
dots << TEXTCOLOR_BLUE;
}
else if (status & Timidity::VOICE_RELEASING)
{
dots << TEXTCOLOR_ORANGE;
}
else if (status & Timidity::VOICE_STOPPING)
{
dots << TEXTCOLOR_RED;
}
else
{
dots << TEXTCOLOR_GREEN;
}
if (!Renderer->voice[i].eg1.env.bUpdating)
{
dots << "+";
}
else
{
dots << ('0' + Renderer->voice[i].eg1.gf1.stage);
}
}
}
CritSec.unlock();
out.Format(TEXTCOLOR_YELLOW"%3d/%3d ", used, Renderer->voices);
out += dots;
if (Renderer->cut_notes | Renderer->lost_notes)
{
out.AppendFormat(TEXTCOLOR_RED" %d/%d", Renderer->cut_notes, Renderer->lost_notes);
}
return out;
}
//==========================================================================
//
//
@ -250,3 +333,8 @@ MIDIDevice *CreateTimidityMIDIDevice(const char *args, int samplerate)
return new TimidityMIDIDevice(args, samplerate);
}
void Timidity_Shutdown()
{
if (instruments) delete instruments;
instruments = nullptr;
}

View file

@ -158,9 +158,9 @@ void I_ShutdownMusic(bool onexit)
S_StopMusic (true);
assert (currSong == nullptr);
}
Timidity::FreeAll();
if (onexit)
{
Timidity_Shutdown();
WildMidi_Shutdown();
TimidityPP_Shutdown();
dumb_exit();

View file

@ -88,6 +88,7 @@ public:
void Timidity_Shutdown();
void TimidityPP_Shutdown();
// Base class for software synthesizer MIDI output devices ------------------

View file

@ -44,7 +44,8 @@
FSoundFontManager sfmanager;
struct timidity_file_FileReader : public TimidityPlus::timidity_file
template<class base>
struct file_interface : public base
{
FileReader fr;
@ -68,6 +69,10 @@ struct timidity_file_FileReader : public TimidityPlus::timidity_file
if (!fr.isOpen()) return 0;
return (long)fr.Tell();
}
void close()
{
delete this;
}
};
@ -157,12 +162,30 @@ struct TimidityPlus::timidity_file* FSoundFontReader::open_timidityplus_file(con
FileReader fr = Open(name, filename);
if (!fr.isOpen()) return nullptr;
auto tf = new timidity_file_FileReader;
auto tf = new file_interface<TimidityPlus::timidity_file>;
tf->fr = std::move(fr);
tf->filename = std::move(filename);
return tf;
}
//==========================================================================
//
// This is the interface function for Timidity(GUS)
//
//==========================================================================
struct Timidity::timidity_file* FSoundFontReader::open_timidity_file(const char* name)
{
std::string filename;
FileReader fr = Open(name, filename);
if (!fr.isOpen()) return nullptr;
auto tf = new file_interface<Timidity::timidity_file>;
tf->fr = std::move(fr);
tf->filename = std::move(filename);
return tf;
}
//==========================================================================
//
@ -284,14 +307,16 @@ FPatchSetReader::FPatchSetReader(const char *filename)
}
}
FPatchSetReader::FPatchSetReader()
FPatchSetReader::FPatchSetReader(FileReader &reader)
{
// This constructor is for reading DMXGUS
mAllowAbsolutePaths = true;
dmxgus = std::move(reader);
}
FileReader FPatchSetReader::OpenMainConfigFile()
{
if (dmxgus.isOpen()) return std::move(dmxgus);
FileReader fr;
fr.OpenFile(mFullPathToConfig);
return fr;

View file

@ -4,6 +4,7 @@
#include "w_wad.h"
#include "files.h"
#include "timiditypp/timidity_file.h"
#include "timidity/timidity_file.h"
enum
{
@ -27,7 +28,7 @@ struct FSoundFontInfo
//
//==========================================================================
class FSoundFontReader : public TimidityPlus::SoundFontReaderInterface
class FSoundFontReader : public TimidityPlus::SoundFontReaderInterface, public Timidity::SoundFontReaderInterface
{
protected:
// This is only doable for loose config files that get set as sound fonts. All other cases read from a contained environment where this does not apply.
@ -59,11 +60,21 @@ public:
}
virtual FileReader Open(const char* name, std::string &filename);
virtual struct TimidityPlus::timidity_file* open_timidityplus_file(const char* name);
virtual void timidityplus_add_path(const char* name)
// Timidity++ interface
struct TimidityPlus::timidity_file* open_timidityplus_file(const char* name) override;
void timidityplus_add_path(const char* name) override
{
return AddPath(name);
}
// Timidity(GUS) interface - essentially the same but different namespace
virtual struct Timidity::timidity_file* open_timidity_file(const char* name) override;
virtual void timidity_add_path(const char* name) override
{
return AddPath(name);
}
};
//==========================================================================
@ -131,9 +142,10 @@ class FPatchSetReader : public FSoundFontReader
{
FString mBasePath;
FString mFullPathToConfig;
FileReader dmxgus;
public:
FPatchSetReader();
FPatchSetReader(FileReader &reader);
FPatchSetReader(const char *filename);
virtual FileReader OpenMainConfigFile() override;
virtual FileReader OpenFile(const char *name) override;

View file

@ -24,7 +24,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <exception>
#include "timidity.h"
namespace Timidity

142
src/sound/timidity/common.h Normal file
View file

@ -0,0 +1,142 @@
#pragma once
namespace Timidity
{
/*
config.h
*/
/* Acoustic Grand Piano seems to be the usual default instrument. */
#define DEFAULT_PROGRAM 0
/* 9 here is MIDI channel 10, which is the standard percussion channel.
Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too.
On the other hand, some files know that 16 is not a drum channel and
try to play music on it. This is now a runtime option, so this isn't
a critical choice anymore. */
#define DEFAULT_DRUMCHANNELS (1<<9)
/*#define DEFAULT_DRUMCHANNELS ((1<<9) | (1<<15))*/
#define MAXCHAN 16
#define MAXNOTE 128
/* 1000 here will give a control ratio of 22:1 with 22 kHz output.
Higher CONTROLS_PER_SECOND values allow more accurate rendering
of envelopes and tremolo. The cost is CPU time. */
#define CONTROLS_PER_SECOND 1000
/* A scalar applied to the final mix to try and approximate the
volume level of FMOD's built-in MIDI player. */
#define FINAL_MIX_SCALE 0.5
/* How many bits to use for the fractional part of sample positions.
This affects tonal accuracy. The entire position counter must fit
in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of
a sample is 1048576 samples (2 megabytes in memory). The GUS gets
by with just 9 bits and a little help from its friends...
"The GUS does not SUCK!!!" -- a happy user :) */
#define FRACTION_BITS 12
/* For some reason the sample volume is always set to maximum in all
patch files. Define this for a crude adjustment that may help
equalize instrument volumes. */
//#define ADJUST_SAMPLE_VOLUMES
/* The number of samples to use for ramping out a dying note. Affects
click removal. */
#define MAX_DIE_TIME 20
/**************************************************************************/
/* Anything below this shouldn't need to be changed unless you're porting
to a new machine with other than 32-bit, big-endian words. */
/**************************************************************************/
/* change FRACTION_BITS above, not these */
#define INTEGER_BITS (32 - FRACTION_BITS)
#define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS)
#define FRACTION_MASK (~ INTEGER_MASK)
#define MAX_SAMPLE_SIZE (1 << INTEGER_BITS)
/* This is enforced by some computations that must fit in an int */
#define MAX_CONTROL_RATIO 255
#define MAX_AMPLIFICATION 800
#define FINAL_VOLUME(v) (v)
#define FSCALE(a,b) ((a) * (float)(1<<(b)))
#define FSCALENEG(a,b) ((a) * (1.0L / (float)(1<<(b))))
/* Vibrato and tremolo Choices of the Day */
#define SWEEP_TUNING 38
#define VIBRATO_AMPLITUDE_TUNING 1.0
#define VIBRATO_RATE_TUNING 38
#define TREMOLO_AMPLITUDE_TUNING 1.0
#define TREMOLO_RATE_TUNING 38
#define SWEEP_SHIFT 16
#define RATE_SHIFT 5
#ifndef PI
#define PI 3.14159265358979323846
#endif
#if defined(__GNUC__) && !defined(__clang__) && (defined(__i386__) || defined(__x86_64__))
// [RH] MinGW's pow() function is terribly slow compared to VC8's
// (I suppose because it's using an old version from MSVCRT.DLL).
// On an Opteron running x86-64 Linux, this also ended up being about
// 100 cycles faster than libm's pow(), which is why I'm using this
// for GCC in general and not just for MinGW.
// [CE] Clang doesn't yet support some inline ASM operations so I disabled it for that instance
extern __inline__ double pow_x87_inline(double x,double y)
{
double result;
if (y == 0)
{
return 1;
}
if (x == 0)
{
if (y > 0)
{
return 0;
}
else
{
union { double fp; long long ip; } infinity;
infinity.ip = 0x7FF0000000000000ll;
return infinity.fp;
}
}
__asm__ (
"fyl2x\n\t"
"fld %%st(0)\n\t"
"frndint\n\t"
"fxch\n\t"
"fsub %%st(1),%%st(0)\n\t"
"f2xm1\n\t"
"fld1\n\t"
"faddp\n\t"
"fxch\n\t"
"fld1\n\t"
"fscale\n\t"
"fstp %%st(1)\n\t"
"fmulp\n\t"
: "=t" (result)
: "0" (x), "u" (y)
: "st(1)", "st(7)" );
return result;
}
#define pow pow_x87_inline
#endif
/*
common.h
*/
extern void *safe_malloc(size_t count);
}

View file

@ -30,17 +30,18 @@
#include <memory>
#include "timidity.h"
//#include "templates.h"
#include "gf1patch.h"
#include "i_soundfont.h"
#include "timidity_file.h"
#include "t_swap.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
namespace Timidity
{
extern std::unique_ptr<FSoundFontReader> gus_sfreader;
extern Instrument *load_instrument_dls(Renderer *song, int drum, int bank, int instrument);
Instrument *load_instrument_dls(Renderer *song, int drum, int bank, int instrument);
Instrument::Instrument()
: samples(0), sample(NULL)
@ -84,22 +85,22 @@ ToneBank::~ToneBank()
}
}
int convert_tremolo_sweep(Renderer *song, uint8_t sweep)
int Renderer::convert_tremolo_sweep(uint8_t sweep)
{
if (sweep == 0)
return 0;
return
int(((song->control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (song->rate * sweep));
int(((control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (rate * sweep));
}
int convert_vibrato_sweep(Renderer *song, uint8_t sweep, int vib_control_ratio)
int Renderer::convert_vibrato_sweep(uint8_t sweep, int vib_control_ratio)
{
if (sweep == 0)
return 0;
return
(int) (FSCALE((double) (vib_control_ratio) * SWEEP_TUNING, SWEEP_SHIFT) / (song->rate * sweep));
(int)(FSCALE((double)(vib_control_ratio)* SWEEP_TUNING, SWEEP_SHIFT) / (rate * sweep));
/* this was overflowing with seashore.pat
@ -107,17 +108,17 @@ int convert_vibrato_sweep(Renderer *song, uint8_t sweep, int vib_control_ratio)
*/
}
int convert_tremolo_rate(Renderer *song, uint8_t rate)
int Renderer::convert_tremolo_rate(uint8_t rate)
{
return
int(((song->control_ratio * rate) << RATE_SHIFT) / (TREMOLO_RATE_TUNING * song->rate));
int(((control_ratio * rate) << RATE_SHIFT) / (TREMOLO_RATE_TUNING * rate));
}
int convert_vibrato_rate(Renderer *song, uint8_t rate)
int Renderer::convert_vibrato_rate(uint8_t rate)
{
/* Return a suitable vibrato_control_ratio value */
return
int((VIBRATO_RATE_TUNING * song->rate) / (rate * 2 * VIBRATO_SAMPLE_INCREMENTS));
int((VIBRATO_RATE_TUNING * rate) / (rate * 2 * VIBRATO_SAMPLE_INCREMENTS));
}
static void reverse_data(sample_t *sp, int ls, int le)
@ -144,7 +145,7 @@ static void reverse_data(sample_t *sp, int ls, int le)
undefined.
TODO: do reverse loops right */
static Instrument *load_instrument(Renderer *song, const char *name, int percussion,
Instrument *Renderer::load_instrument(const char *name, int percussion,
int panning, int note_to_use,
int strip_loop, int strip_envelope,
int strip_tail)
@ -157,23 +158,24 @@ static Instrument *load_instrument(Renderer *song, const char *name, int percuss
GF1PatchData patch_data;
int i, j;
bool noluck = false;
auto reader = instruments->sfreader;
if (!name || gus_sfreader == nullptr) return nullptr;
if (!name || reader == nullptr) return nullptr;
/* Open patch file */
auto fp = gus_sfreader->LookupFile(name).first;
if (!fp.isOpen())
auto fp = reader->open_timidity_file(name);
if (!fp)
{
/* Try with various extensions */
std::string tmp = name;
tmp += ".pat";
fp = gus_sfreader->LookupFile(tmp.c_str()).first;
if (!fp.isOpen())
fp = reader->open_timidity_file(tmp.c_str());
if (!fp)
{
#ifndef _WIN32 // Windows isn't case-sensitive.
std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char c){ return toupper(c); } );
fp = gus_sfreader->LookupFile(tmp.c_str()).first;
if (!fp.isOpen())
fp = reader->open_timidity_file(tmp.c_str());
if (!fp)
#endif
{
noluck = true;
@ -183,7 +185,7 @@ static Instrument *load_instrument(Renderer *song, const char *name, int percuss
if (noluck)
{
cmsg(CMSG_ERROR, VERB_NORMAL, "Instrument `%s' can't be found.\n", name);
cmsg(CMSG_ERROR, VERB_DEBUG, "Instrument `%s' can't be found.\n", name);
return 0;
}
@ -191,23 +193,26 @@ static Instrument *load_instrument(Renderer *song, const char *name, int percuss
/* Read some headers and do cursory sanity checks. */
if (sizeof(header) != fp.Read(&header, sizeof(header)))
if (sizeof(header) != fp->read(&header, sizeof(header)))
{
failread:
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: Error reading instrument.\n", name);
fp->close();
return 0;
}
if (strncmp(header.Header, GF1_HEADER_TEXT, HEADER_SIZE - 4) != 0)
{
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: Not an instrument.\n", name);
fp->close();
return 0;
}
if (strcmp(header.Header + 8, "110") < 0)
{
cmsg(CMSG_ERROR, VERB_NORMAL, "%s: Is an old and unsupported patch version.\n", name);
fp->close();
return 0;
}
if (sizeof(idata) != fp.Read(&idata, sizeof(idata)))
if (sizeof(idata) != fp->read(&idata, sizeof(idata)))
{
goto failread;
}
@ -220,16 +225,18 @@ failread:
if (header.Instruments != 1 && header.Instruments != 0) /* instruments. To some patch makers, 0 means 1 */
{
cmsg(CMSG_ERROR, VERB_NORMAL, "Can't handle patches with %d instruments.\n", header.Instruments);
fp->close();
return 0;
}
if (idata.Layers != 1 && idata.Layers != 0) /* layers. What's a layer? */
{
cmsg(CMSG_ERROR, VERB_NORMAL, "Can't handle instruments with %d layers.\n", idata.Layers);
fp->close();
return 0;
}
if (sizeof(layer_data) != fp.Read(&layer_data, sizeof(layer_data)))
if (sizeof(layer_data) != fp->read(&layer_data, sizeof(layer_data)))
{
goto failread;
}
@ -237,6 +244,7 @@ failread:
if (layer_data.Samples == 0)
{
cmsg(CMSG_ERROR, VERB_NORMAL, "Instrument has 0 samples.\n");
fp->close();
return 0;
}
@ -246,11 +254,12 @@ failread:
memset(ip->sample, 0, sizeof(Sample) * layer_data.Samples);
for (i = 0; i < layer_data.Samples; ++i)
{
if (sizeof(patch_data) != fp.Read(&patch_data, sizeof(patch_data)))
if (sizeof(patch_data) != fp->read(&patch_data, sizeof(patch_data)))
{
fail:
cmsg(CMSG_ERROR, VERB_NORMAL, "Error reading sample %d.\n", i);
delete ip;
fp->close();
return 0;
}
@ -276,7 +285,7 @@ fail:
{
sp->panning = (panning & 0x7f) * 1000 / 127 - 500;
}
song->compute_pan((sp->panning + 500) / 1000.0, INST_GUS, sp->left_offset, sp->right_offset);
compute_pan((sp->panning + 500) / 1000.0, INST_GUS, sp->left_offset, sp->right_offset);
/* tremolo */
if (patch_data.TremoloRate == 0 || patch_data.TremoloDepth == 0)
@ -288,8 +297,8 @@ fail:
}
else
{
sp->tremolo_sweep_increment = convert_tremolo_sweep(song, patch_data.TremoloSweep);
sp->tremolo_phase_increment = convert_tremolo_rate(song, patch_data.TremoloRate);
sp->tremolo_sweep_increment = convert_tremolo_sweep(patch_data.TremoloSweep);
sp->tremolo_phase_increment = convert_tremolo_rate(patch_data.TremoloRate);
sp->tremolo_depth = patch_data.TremoloDepth;
cmsg(CMSG_INFO, VERB_DEBUG, " * tremolo: sweep %d, phase %d, depth %d\n",
sp->tremolo_sweep_increment, sp->tremolo_phase_increment, sp->tremolo_depth);
@ -305,8 +314,8 @@ fail:
}
else
{
sp->vibrato_control_ratio = convert_vibrato_rate(song, patch_data.VibratoRate);
sp->vibrato_sweep_increment = convert_vibrato_sweep(song, patch_data.VibratoSweep, sp->vibrato_control_ratio);
sp->vibrato_control_ratio = convert_vibrato_rate(patch_data.VibratoRate);
sp->vibrato_sweep_increment = convert_vibrato_sweep(patch_data.VibratoSweep, sp->vibrato_control_ratio);
sp->vibrato_depth = patch_data.VibratoDepth;
cmsg(CMSG_INFO, VERB_DEBUG, " * vibrato: sweep %d, ctl %d, depth %d\n",
sp->vibrato_sweep_increment, sp->vibrato_control_ratio, sp->vibrato_depth);
@ -412,7 +421,7 @@ fail:
}
sp->data = (sample_t *)safe_malloc(sp->data_length);
if (sp->data_length != fp.Read(sp->data, sp->data_length))
if (sp->data_length != fp->read(sp->data, sp->data_length))
goto fail;
convert_sample_data(sp, sp->data);
@ -449,7 +458,7 @@ fail:
and it's not looped, we can resample it now. */
if (sp->scale_factor == 0 && !(sp->modes & PATCH_LOOPEN))
{
pre_resample(song, sp);
pre_resample(this, sp);
}
if (strip_tail == 1)
@ -459,6 +468,7 @@ fail:
sp->data_length = sp->loop_end;
}
}
fp->close();
return ip;
}
@ -561,14 +571,14 @@ void convert_sample_data(Sample *sp, const void *data)
sp->data = newdata;
}
static int fill_bank(Renderer *song, int dr, int b)
int Renderer::fill_bank(int dr, int b)
{
int i, errors = 0;
ToneBank *bank = ((dr) ? drumset[b] : tonebank[b]);
ToneBank *bank = ((dr) ? instruments->drumset[b] : instruments->tonebank[b]);
if (bank == NULL)
{
cmsg(CMSG_ERROR, VERB_NORMAL,
"Huh. Tried to load instruments in non-existent %s %d\n",
"Tried to load instruments in non-existent %s %d\n",
(dr) ? "drumset" : "tone bank", b);
return 0;
}
@ -577,22 +587,22 @@ static int fill_bank(Renderer *song, int dr, int b)
if (bank->instrument[i] == MAGIC_LOAD_INSTRUMENT)
{
bank->instrument[i] = NULL;
bank->instrument[i] = load_instrument_dls(song, dr, b, i);
bank->instrument[i] = load_instrument_dls(this, dr, b, i);
if (bank->instrument[i] != NULL)
{
continue;
}
Instrument *ip;
ip = load_instrument_font_order(song, 0, dr, b, i);
ip = load_instrument_font_order(0, dr, b, i);
if (ip == NULL)
{
if (bank->tone[i].fontbank >= 0)
{
ip = load_instrument_font(song, bank->tone[i].name, dr, b, i);
ip = load_instrument_font(bank->tone[i].name, dr, b, i);
}
else
{
ip = load_instrument(song, bank->tone[i].name,
ip = load_instrument(bank->tone[i].name,
(dr) ? 1 : 0,
bank->tone[i].pan,
(bank->tone[i].note != -1) ? bank->tone[i].note : ((dr) ? i : -1),
@ -602,7 +612,7 @@ static int fill_bank(Renderer *song, int dr, int b)
}
if (ip == NULL)
{
ip = load_instrument_font_order(song, 1, dr, b, i);
ip = load_instrument_font_order(1, dr, b, i);
}
}
bank->instrument[i] = ip;
@ -610,14 +620,14 @@ static int fill_bank(Renderer *song, int dr, int b)
{
if (bank->tone[i].name.IsEmpty())
{
cmsg(CMSG_WARNING, (b != 0) ? VERB_VERBOSE : VERB_NORMAL,
cmsg(CMSG_WARNING, (b != 0) ? VERB_VERBOSE : VERB_DEBUG,
"No instrument mapped to %s %d, program %d%s\n",
(dr) ? "drum set" : "tone bank", b, i,
(b != 0) ? "" : " - this instrument will not be heard");
}
else
{
cmsg(CMSG_ERROR, VERB_NORMAL,
cmsg(CMSG_ERROR, VERB_DEBUG,
"Couldn't load instrument %s (%s %d, program %d)\n",
bank->tone[i].name.GetChars(),
(dr) ? "drum set" : "tone bank", b, i);
@ -626,9 +636,9 @@ static int fill_bank(Renderer *song, int dr, int b)
{
/* Mark the corresponding instrument in the default
bank / drumset for loading (if it isn't already) */
if (((dr) ? drumset[0] : tonebank[0])->instrument[i] != NULL)
if (((dr) ? instruments->drumset[0] : instruments->tonebank[0])->instrument[i] != NULL)
{
((dr) ? drumset[0] : tonebank[0])->instrument[i] = MAGIC_LOAD_INSTRUMENT;
((dr) ? instruments->drumset[0] : instruments->tonebank[0])->instrument[i] = MAGIC_LOAD_INSTRUMENT;
}
}
errors++;
@ -643,15 +653,15 @@ int Renderer::load_missing_instruments()
int i = MAXBANK, errors = 0;
while (i--)
{
if (tonebank[i] != NULL)
errors += fill_bank(this, 0,i);
if (drumset[i] != NULL)
errors += fill_bank(this, 1,i);
if (instruments->tonebank[i] != NULL)
errors += fill_bank(0, i);
if (instruments->drumset[i] != NULL)
errors += fill_bank(1, i);
}
return errors;
}
void free_instruments()
void Instruments::free_instruments()
{
int i = MAXBANK;
while (i--)
@ -669,10 +679,39 @@ void free_instruments()
}
}
Instruments::Instruments(SoundFontReaderInterface *reader)
{
sfreader = reader;
tonebank[0] = new ToneBank;
drumset[0] = new ToneBank;
}
Instruments::~Instruments()
{
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;
}
}
if (sfreader != nullptr) delete sfreader;
sfreader = nullptr;
}
int Renderer::set_default_instrument(const char *name)
{
Instrument *ip;
if ((ip = load_instrument(this, name, 0, -1, -1, 0, 0, 0)) == NULL)
if ((ip = load_instrument(name, 0, -1, -1, 0, 0, 0)) == NULL)
{
return -1;
}
@ -685,4 +724,5 @@ int Renderer::set_default_instrument(const char *name)
return 0;
}
}

View file

@ -0,0 +1,193 @@
#pragma once
namespace Timidity
{
/*
instrum.h
*/
enum
{
PATCH_16 = (1<<0),
PATCH_UNSIGNED = (1<<1),
PATCH_LOOPEN = (1<<2),
PATCH_BIDIR = (1<<3),
PATCH_BACKWARD = (1<<4),
PATCH_SUSTAIN = (1<<5),
PATCH_NO_SRELEASE = (1<<6),
PATCH_FAST_REL = (1<<7),
};
struct Sample
{
int32_t
loop_start, loop_end, data_length,
sample_rate;
float
low_freq, high_freq, root_freq;
union
{
struct
{
uint8_t rate[6], offset[6];
} gf1;
struct
{
short delay_vol;
short attack_vol;
short hold_vol;
short decay_vol;
short sustain_vol;
short release_vol;
} sf2;
} envelope;
sample_t *data;
int32_t
tremolo_sweep_increment, tremolo_phase_increment,
vibrato_sweep_increment, vibrato_control_ratio;
uint8_t
tremolo_depth, vibrato_depth,
low_vel, high_vel,
type;
uint16_t
modes;
int16_t
panning;
uint16_t
scale_factor, key_group;
int16_t
scale_note;
bool
self_nonexclusive;
float
left_offset, right_offset;
// SF2 stuff
int16_t tune;
int8_t velocity;
float initial_attenuation;
};
/* Magic file words */
#define ID_RIFF MAKE_ID('R','I','F','F')
#define ID_LIST MAKE_ID('L','I','S','T')
#define ID_INFO MAKE_ID('I','N','F','O')
#define ID_sfbk MAKE_ID('s','f','b','k')
#define ID_sdta MAKE_ID('s','d','t','a')
#define ID_pdta MAKE_ID('p','d','t','a')
#define ID_ifil MAKE_ID('i','f','i','l')
#define ID_iver MAKE_ID('i','v','e','r')
#define ID_irom MAKE_ID('i','r','o','m')
#define ID_smpl MAKE_ID('s','m','p','l')
#define ID_sm24 MAKE_ID('s','m','2','4')
#define ID_phdr MAKE_ID('p','h','d','r')
#define ID_pbag MAKE_ID('p','b','a','g')
#define ID_pmod MAKE_ID('p','m','o','d')
#define ID_pgen MAKE_ID('p','g','e','n')
#define ID_inst MAKE_ID('i','n','s','t')
#define ID_ibag MAKE_ID('i','b','a','g')
#define ID_imod MAKE_ID('i','m','o','d')
#define ID_igen MAKE_ID('i','g','e','n')
#define ID_shdr MAKE_ID('s','h','d','r')
/* Instrument definitions */
enum
{
INST_GUS,
INST_DLS,
INST_SF2
};
struct Instrument
{
Instrument();
~Instrument();
int samples;
Sample *sample;
};
struct ToneBankElement
{
ToneBankElement() :
note(0), pan(0), strip_loop(0), strip_envelope(0), strip_tail(0)
{}
FString name;
int note, pan, fontbank, fontpreset, fontnote;
int8_t strip_loop, strip_envelope, strip_tail;
};
/* A hack to delay instrument loading until after reading the entire MIDI file. */
#define MAGIC_LOAD_INSTRUMENT ((Instrument *)(-1))
enum
{
MAXPROG = 128,
MAXBANK = 128,
SPECIAL_PROGRAM = -1
};
struct ToneBank
{
ToneBank();
~ToneBank();
ToneBankElement *tone;
Instrument *instrument[MAXPROG];
};
/*
instrum_font.cpp
*/
class FontFile
{
public:
FontFile(const char *filename);
virtual ~FontFile();
std::string Filename;
FontFile *Next;
virtual Instrument *LoadInstrument(struct Renderer *song, int drum, int bank, int program) = 0;
virtual Instrument *LoadInstrumentOrder(struct Renderer *song, int order, int drum, int bank, int program) = 0;
virtual void SetOrder(int order, int drum, int bank, int program) = 0;
virtual void SetAllOrders(int order) = 0;
};
class Instruments
{
public:
SoundFontReaderInterface *sfreader;
ToneBank* tonebank[MAXBANK] = {};
ToneBank* drumset[MAXBANK] = {};
FontFile* Fonts = nullptr;
Instruments(SoundFontReaderInterface* reader);
~Instruments();
int LoadConfig() { return read_config_file(nullptr); }
int read_config_file(const char* name);
int LoadDMXGUS(int gus_memsize);
void font_freeall();
FontFile* font_find(const char* filename);
void font_add(const char* filename, int load_order);
void font_remove(const char* filename);
void font_order(int order, int bank, int preset, int keynote);
void convert_sample_data(Sample* sample, const void* data);
void free_instruments();
FontFile* ReadDLS(const char* filename, timidity_file* f);
};
void convert_sample_data(Sample* sp, const void* data);
}

Binary file not shown.

View file

@ -25,8 +25,12 @@
#include <string.h>
#include <math.h>
#include "m_swap.h"
#include "t_swap.h"
#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#ifndef MAKE_ID
#ifndef __BIG_ENDIAN__

View file

@ -3,21 +3,22 @@
#include <memory>
#include "timidity.h"
#include "timidity_file.h"
#include "common.h"
#include "instrum.h"
#include "sf2.h"
#include "i_soundfont.h"
namespace Timidity
{
FontFile *Fonts;
extern std::unique_ptr<FSoundFontReader> gus_sfreader;
FontFile *ReadDLS(const char *filename, FileReader &f)
FontFile *Instruments::ReadDLS(const char *filename, timidity_file *f)
{
return NULL;
}
void font_freeall()
void Instruments::font_freeall()
{
FontFile *font, *next;
@ -29,7 +30,7 @@ void font_freeall()
Fonts = NULL;
}
FontFile *font_find(const char *filename)
FontFile * Instruments::font_find(const char *filename)
{
for (FontFile *font = Fonts; font != NULL; font = font->Next)
{
@ -41,7 +42,7 @@ FontFile *font_find(const char *filename)
return NULL;
}
void font_add(const char *filename, int load_order)
void Instruments::font_add(const char *filename, int load_order)
{
FontFile *font;
@ -52,19 +53,23 @@ void font_add(const char *filename, int load_order)
}
else
{
FileReader fp = gus_sfreader->LookupFile(filename).first;
auto fp = sfreader->open_timidity_file(filename);
if (fp.isOpen())
if (fp)
{
if ((font = ReadSF2(filename, fp)) || (font = ReadDLS(filename, fp)))
{
font->Next = Fonts;
Fonts = font;
font->SetAllOrders(load_order);
}
fp->close();
}
}
}
void font_remove(const char *filename)
void Instruments::font_remove(const char *filename)
{
FontFile *font;
@ -77,7 +82,7 @@ void font_remove(const char *filename)
}
}
void font_order(int order, int bank, int preset, int keynote)
void Instruments::font_order(int order, int bank, int preset, int keynote)
{
for (FontFile *font = Fonts; font != NULL; font = font->Next)
{
@ -85,21 +90,21 @@ void font_order(int order, int bank, int preset, int keynote)
}
}
Instrument *load_instrument_font(struct Renderer *song, const char *font, int drum, int bank, int instr)
Instrument *Renderer::load_instrument_font(const char *font, int drum, int bank, int instr)
{
FontFile *fontfile = font_find(font);
FontFile *fontfile = instruments->font_find(font);
if (fontfile != NULL)
{
return fontfile->LoadInstrument(song, drum, bank, instr);
return fontfile->LoadInstrument(this, drum, bank, instr);
}
return NULL;
}
Instrument *load_instrument_font_order(struct Renderer *song, int order, int drum, int bank, int instr)
Instrument *Renderer::load_instrument_font_order(int order, int drum, int bank, int instr)
{
for (FontFile *font = Fonts; font != NULL; font = font->Next)
for (FontFile *font = instruments->Fonts; font != NULL; font = font->Next)
{
Instrument *ip = font->LoadInstrument(song, drum, bank, instr);
Instrument *ip = font->LoadInstrument(this, drum, bank, instr);
if (ip != NULL)
{
return ip;
@ -111,20 +116,10 @@ Instrument *load_instrument_font_order(struct Renderer *song, int order, int dru
FontFile::FontFile(const char *filename)
: Filename(filename)
{
Next = Fonts;
Fonts = this;
}
FontFile::~FontFile()
{
for (FontFile **probe = &Fonts; *probe != NULL; probe = &(*probe)->Next)
{
if (*probe == this)
{
*probe = Next;
break;
}
}
}
}

View file

@ -5,18 +5,17 @@
#include <memory>
#include "doomdef.h"
#include "m_swap.h"
#include "t_swap.h"
#include "templates.h"
#include "timidity.h"
#include "timidity_file.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "sf2.h"
#include "i_soundfont.h"
namespace Timidity
{
extern std::unique_ptr<FSoundFontReader> gus_sfreader;
}
using namespace Timidity;
#define cindex(identifier) (uint8_t)(((size_t)&((SFGenComposite *)1)->identifier - 1) / 2)
@ -27,7 +26,7 @@ class CBadVer {};
struct ListHandler
{
uint32_t ID;
void (*Parser)(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
void (*Parser)(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
};
enum
@ -161,15 +160,15 @@ static const SFGenComposite DefaultGenerators =
-1 // overridingRootKey
};
static void ParseIfil(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseSmpl(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseSm24(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParsePhdr(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseBag(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseMod(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseGen(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseInst(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseShdr(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen);
static void ParseIfil(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParseSmpl(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParseSm24(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParsePhdr(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParseBag(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParseMod(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParseGen(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParseInst(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
static void ParseShdr(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen);
ListHandler INFOHandlers[] =
{
@ -224,87 +223,87 @@ static int32_t calc_rate(Renderer *song, int diff, double sec)
}
static inline uint32_t read_id(FileReader &f)
static inline uint32_t read_id(timidity_file *f)
{
uint32_t id;
if (f.Read(&id, 4) != 4)
if (f->read(&id, 4) != 4)
{
throw CIOErr();
}
return id;
}
static inline int read_byte(FileReader &f)
static inline int read_byte(timidity_file *f)
{
uint8_t x;
if (f.Read(&x, 1) != 1)
if (f->read(&x, 1) != 1)
{
throw CIOErr();
}
return x;
}
static inline int read_char(FileReader &f)
static inline int read_char(timidity_file *f)
{
int8_t x;
if (f.Read(&x, 1) != 1)
if (f->read(&x, 1) != 1)
{
throw CIOErr();
}
return x;
}
static inline int read_uword(FileReader &f)
static inline int read_uword(timidity_file *f)
{
uint16_t x;
if (f.Read(&x, 2) != 2)
if (f->read(&x, 2) != 2)
{
throw CIOErr();
}
return LittleShort(x);
}
static inline int read_sword(FileReader &f)
static inline int read_sword(timidity_file *f)
{
int16_t x;
if (f.Read(&x, 2) != 2)
if (f->read(&x, 2) != 2)
{
throw CIOErr();
}
return LittleShort(x);
}
static inline uint32_t read_dword(FileReader &f)
static inline uint32_t read_dword(timidity_file *f)
{
uint32_t x;
if (f.Read(&x, 4) != 4)
if (f->read(&x, 4) != 4)
{
throw CIOErr();
}
return LittleLong(x);
}
static inline void read_name(FileReader &f, char name[21])
static inline void read_name(timidity_file *f, char name[21])
{
if (f.Read(name, 20) != 20)
if (f->read(name, 20) != 20)
{
throw CIOErr();
}
name[20] = 0;
}
static inline void skip_chunk(FileReader &f, uint32_t len)
static inline void skip_chunk(timidity_file *f, uint32_t len)
{
// RIFF, like IFF, adds an extra pad byte to the end of
// odd-sized chunks so that new chunks are always on even
// byte boundaries.
if (f.Seek(len + (len & 1), FileReader::SeekCur) != 0)
if (f->seek(len + (len & 1), SEEK_CUR) != 0)
{
throw CIOErr();
}
}
static void check_list(FileReader &f, uint32_t id, uint32_t filelen, uint32_t &chunklen)
static void check_list(timidity_file *f, uint32_t id, uint32_t filelen, uint32_t &chunklen)
{
if (read_id(f) != ID_LIST)
{
@ -321,7 +320,7 @@ static void check_list(FileReader &f, uint32_t id, uint32_t filelen, uint32_t &c
}
}
static void ParseIfil(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseIfil(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
uint16_t major, minor;
@ -340,7 +339,7 @@ static void ParseIfil(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chu
sf2->MinorVersion = minor;
}
static void ParseLIST(SFFile *sf2, FileReader &f, uint32_t chunklen, ListHandler *handlers)
static void ParseLIST(SFFile *sf2, timidity_file *f, uint32_t chunklen, ListHandler *handlers)
{
ListHandler *handler;
uint32_t id;
@ -374,7 +373,7 @@ static void ParseLIST(SFFile *sf2, FileReader &f, uint32_t chunklen, ListHandler
}
}
static void ParseINFO(SFFile *sf2, FileReader &f, uint32_t chunklen)
static void ParseINFO(SFFile *sf2, timidity_file *f, uint32_t chunklen)
{
sf2->MinorVersion = -1;
@ -386,7 +385,7 @@ static void ParseINFO(SFFile *sf2, FileReader &f, uint32_t chunklen)
}
}
static void ParseSdta(SFFile *sf2, FileReader &f, uint32_t chunklen)
static void ParseSdta(SFFile *sf2, timidity_file *f, uint32_t chunklen)
{
ParseLIST(sf2, f, chunklen, SdtaHandlers);
if (sf2->SampleDataOffset == 0)
@ -403,7 +402,7 @@ static void ParseSdta(SFFile *sf2, FileReader &f, uint32_t chunklen)
}
}
static void ParseSmpl(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseSmpl(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
// Only use the first smpl chunk. (Or should we reject files with more than one?)
if (sf2->SampleDataOffset == 0)
@ -412,13 +411,13 @@ static void ParseSmpl(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chu
{ // Chunk must be an even number of bytes.
throw CBadForm();
}
sf2->SampleDataOffset = (uint32_t)f.Tell();
sf2->SampleDataOffset = (uint32_t)f->tell();
sf2->SizeSampleData = chunklen >> 1;
}
skip_chunk(f, chunklen);
}
static void ParseSm24(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseSm24(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
// The sm24 chunk is ignored if the file version is < 2.04
if (sf2->MinorVersion >= 4)
@ -426,19 +425,19 @@ static void ParseSm24(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chu
// Only use the first sm24 chunk. (Or should we reject files with more than one?)
if (sf2->SampleDataLSBOffset == 0)
{
sf2->SampleDataLSBOffset = (uint32_t)f.Tell();
sf2->SampleDataLSBOffset = (uint32_t)f->tell();
sf2->SizeSampleDataLSB = chunklen;
}
}
skip_chunk(f, chunklen);
}
static void ParsePdta(SFFile *sf2, FileReader &f, uint32_t chunklen)
static void ParsePdta(SFFile *sf2, timidity_file *f, uint32_t chunklen)
{
ParseLIST(sf2, f, chunklen, PdtaHandlers);
}
static void ParsePhdr(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParsePhdr(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
SFPreset *preset;
@ -476,7 +475,7 @@ static void ParsePhdr(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chu
}
}
static void ParseBag(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseBag(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
SFBag *bags, *bag;
uint16_t prev_mod = 0;
@ -538,7 +537,7 @@ static void ParseBag(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chun
}
}
static void ParseMod(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseMod(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
// Section 7.4, page 23:
// It [the PMOD sub-chunk] is always a multiple of ten bytes in length,
@ -551,7 +550,7 @@ static void ParseMod(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chun
skip_chunk(f, chunklen);
}
static void ParseGen(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseGen(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
SFGenList *gens, *gen;
int numgens;
@ -603,7 +602,7 @@ static void ParseGen(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chun
}
}
static void ParseInst(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseInst(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
int i;
SFInst *inst;
@ -638,7 +637,7 @@ static void ParseInst(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chu
}
}
static void ParseShdr(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chunklen)
static void ParseShdr(SFFile *sf2, timidity_file *f, uint32_t chunkid, uint32_t chunklen)
{
int i;
SFSample *sample;
@ -697,7 +696,7 @@ static void ParseShdr(SFFile *sf2, FileReader &f, uint32_t chunkid, uint32_t chu
}
SFFile *ReadSF2(const char *filename, FileReader &f)
SFFile *ReadSF2(const char *filename, timidity_file *f)
{
SFFile *sf2 = NULL;
uint32_t filelen;
@ -746,15 +745,15 @@ SFFile *ReadSF2(const char *filename, FileReader &f)
}
catch (CIOErr)
{
Printf("Error reading %s: %s\n", filename, strerror(errno));
cmsg(CMSG_ERROR, VERB_NORMAL, "Error reading %s: %s\n", filename, strerror(errno));
}
catch (CBadForm)
{
Printf("%s is corrupted.\n", filename);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s is corrupted.\n", filename);
}
catch (CBadVer)
{
Printf("%s is not a SoundFont version 2 file.\n", filename);
cmsg(CMSG_ERROR, VERB_NORMAL, "%s is not a SoundFont version 2 file.\n", filename);
}
if (sf2 != NULL)
{
@ -1256,7 +1255,7 @@ Instrument *SFFile::LoadPercussion(Renderer *song, SFPerc *perc)
SFSample *sfsamp = &Samples[Percussion[i].Generators.sampleID];
if (sfsamp->InMemoryData == NULL)
{
LoadSample(sfsamp);
LoadSample(song, sfsamp);
}
if (sfsamp->InMemoryData != NULL)
{
@ -1342,7 +1341,7 @@ Instrument *SFFile::LoadPreset(Renderer *song, SFPreset *preset)
sfsamp = &Samples[InstrBags[j].Target];
if (sfsamp->InMemoryData == NULL)
{
LoadSample(sfsamp);
LoadSample(song, sfsamp);
}
if (sfsamp->InMemoryData != NULL)
{
@ -1505,32 +1504,37 @@ void SFFile::ApplyGeneratorsToRegion(SFGenComposite *gen, SFSample *sfsamp, Rend
//
//===========================================================================
void SFFile::LoadSample(SFSample *sample)
void SFFile::LoadSample(Renderer *song, SFSample *sample)
{
FileReader fp = gus_sfreader->LookupFile(Filename.c_str()).first;
auto fp = song->instruments->sfreader->open_timidity_file(Filename.c_str());
uint32_t i;
if (!fp.isOpen())
if (!fp)
{
return;
}
sample->InMemoryData = new float[sample->End - sample->Start + 1];
fp.Seek(SampleDataOffset + sample->Start * 2, FileReader::SeekSet);
fp->seek(SampleDataOffset + sample->Start * 2, SEEK_SET);
// Load 16-bit sample data.
for (i = 0; i < sample->End - sample->Start; ++i)
{
int16_t samp = fp.ReadInt16();
uint16_t samp;
fp->read(&samp, 2);
samp = LittleShort(samp);
sample->InMemoryData[i] = samp / 32768.f;
}
if (SampleDataLSBOffset != 0)
{ // Load lower 8 bits of 24-bit sample data.
fp.Seek(SampleDataLSBOffset + sample->Start, FileReader::SeekSet);
fp->seek(SampleDataLSBOffset + sample->Start, SEEK_SET);
for (i = 0; i < sample->End - sample->Start; ++i)
{
uint8_t samp = fp.ReadUInt8();
uint8_t samp;
fp->read(&samp, 1);
sample->InMemoryData[i] = ((((int32_t(sample->InMemoryData[i] * 32768) << 8) | samp) << 8) >> 8) / 8388608.f;
}
}
// Final 0 byte is for interpolation.
sample->InMemoryData[i] = 0;
fp->close();
}
}

View file

@ -26,6 +26,10 @@
#include <stdlib.h>
#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
namespace Timidity
{

View file

@ -27,10 +27,33 @@
#include <math.h>
#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
namespace Timidity
{
bool Envelope::Update(struct Voice* v)
{
if (Type == INST_GUS)
return gf1.Update(v);
return sf2.Update(v);
}
void Envelope::ApplyToAmp(struct Voice* v)
{
if (Type == INST_GUS)
return gf1.ApplyToAmp(v);
return sf2.ApplyToAmp(v);
}
void Envelope::Release(struct Voice* v)
{
if (Type == INST_GUS)
return gf1.Release(v);
return sf2.Release(v);
}
void Renderer::reset_voices()
{
memset(voice, 0, sizeof(voice[0]) * voices);
@ -342,9 +365,9 @@ void Renderer::start_note(int chan, int note, int vel)
note &= 0x7f;
if (ISDRUMCHANNEL(chan))
{
if (NULL == drumset[bank] || NULL == (ip = drumset[bank]->instrument[note]))
if (NULL == instruments->drumset[bank] || NULL == (ip = instruments->drumset[bank]->instrument[note]))
{
if (!(ip = drumset[0]->instrument[note]))
if (!(ip = instruments->drumset[0]->instrument[note]))
return; /* No instrument? Then we can't play. */
}
assert(ip != MAGIC_LOAD_INSTRUMENT);
@ -364,9 +387,9 @@ void Renderer::start_note(int chan, int note, int vel)
{
ip = default_instrument;
}
else if (NULL == tonebank[bank] || NULL == (ip = tonebank[bank]->instrument[prog]))
else if (NULL == instruments->tonebank[bank] || NULL == (ip = instruments->tonebank[bank]->instrument[prog]))
{
if (NULL == (ip = tonebank[0]->instrument[prog]))
if (NULL == (ip = instruments->tonebank[0]->instrument[prog]))
return; /* No instrument? Then we can't play. */
}
assert(ip != MAGIC_LOAD_INSTRUMENT);

View file

@ -0,0 +1,122 @@
#pragma once
namespace Timidity
{
/*
mix.h
*/
extern void mix_voice(struct Renderer *song, float *buf, struct Voice *v, int c);
extern int recompute_envelope(struct Voice *v);
extern void apply_envelope_to_amp(struct Voice *v);
/*
playmidi.h
*/
/* Midi events */
enum
{
ME_NOTEOFF = 0x80,
ME_NOTEON = 0x90,
ME_KEYPRESSURE = 0xA0,
ME_CONTROLCHANGE = 0xB0,
ME_PROGRAM = 0xC0,
ME_CHANNELPRESSURE = 0xD0,
ME_PITCHWHEEL = 0xE0
};
/* Controllers */
enum
{
CTRL_BANK_SELECT = 0,
CTRL_DATA_ENTRY = 6,
CTRL_VOLUME = 7,
CTRL_PAN = 10,
CTRL_EXPRESSION = 11,
CTRL_SUSTAIN = 64,
CTRL_HARMONICCONTENT = 71,
CTRL_RELEASETIME = 72,
CTRL_ATTACKTIME = 73,
CTRL_BRIGHTNESS = 74,
CTRL_REVERBERATION = 91,
CTRL_CHORUSDEPTH = 93,
CTRL_NRPN_LSB = 98,
CTRL_NRPN_MSB = 99,
CTRL_RPN_LSB = 100,
CTRL_RPN_MSB = 101,
CTRL_ALL_SOUNDS_OFF = 120,
CTRL_RESET_CONTROLLERS = 121,
CTRL_ALL_NOTES_OFF = 123
};
/* RPNs */
enum
{
RPN_PITCH_SENS = 0x0000,
RPN_FINE_TUNING = 0x0001,
RPN_COARSE_TUNING = 0x0002,
RPN_RESET = 0x3fff
};
/* Causes the instrument's default panning to be used. */
#define NO_PANNING -1
/* Voice status options: */
enum
{
VOICE_RUNNING = (1<<0),
VOICE_SUSTAINING = (1<<1),
VOICE_RELEASING = (1<<2),
VOICE_STOPPING = (1<<3),
VOICE_LPE = (1<<4),
NOTE_SUSTAIN = (1<<5),
};
/* Envelope stages: */
enum
{
GF1_ATTACK,
GF1_HOLD,
GF1_DECAY,
GF1_RELEASE,
GF1_RELEASEB,
GF1_RELEASEC
};
enum
{
SF2_DELAY,
SF2_ATTACK,
SF2_HOLD,
SF2_DECAY,
SF2_SUSTAIN,
SF2_RELEASE,
SF2_FINISHED
};
#define ISDRUMCHANNEL(c) ((drumchannels & (1<<(c))))
/*
resample.h
*/
extern sample_t *resample_voice(struct Renderer *song, Voice *v, int *countptr);
extern void pre_resample(struct Renderer *song, Sample *sp);
/*
tables.h
*/
const double log_of_2 = 0.69314718055994529;
#define sine(x) (sin((2*PI/1024.0) * (x)))
#define note_to_freq(x) (float(8175.7989473096690661233836992789 * pow(2.0, (x) / 12.0)))
#define freq_to_note(x) (log((x) / 8175.7989473096690661233836992789) * (12.0 / log_of_2))
#define calc_gf1_amp(x) (pow(2.0,((x)*16.0 - 16.0))) // Actual GUS equation
}

View file

@ -26,6 +26,10 @@
#include <stdlib.h>
#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
namespace Timidity
{
@ -561,10 +565,6 @@ void pre_resample(Renderer *song, Sample *sp)
if (sp->scale_factor != 0)
return;
cmsg(CMSG_INFO, VERB_NOISY, " * pre-resampling for note %d (%s%d)\n",
sp->scale_note,
note_name[sp->scale_note % 12], (sp->scale_note & 0x7F) / 12);
a = (sp->sample_rate * note_to_freq(sp->scale_note)) / (sp->root_freq * song->rate);
if (a <= 0)
return;

View file

@ -1,4 +1,8 @@
#pragma once
namespace Timidity
{
typedef uint16_t SFGenerator;
struct timidity_file;
struct SFRange
{
@ -268,12 +272,12 @@ struct SFPerc
// Container for all parameters from a SoundFont file
struct SFFile : public Timidity::FontFile
struct SFFile : public FontFile
{
SFFile(const char * filename);
~SFFile();
Timidity::Instrument *LoadInstrument(struct Timidity::Renderer *song, int drum, int bank, int program);
Timidity::Instrument *LoadInstrumentOrder(struct Timidity::Renderer *song, int order, int drum, int bank, int program);
Instrument *LoadInstrument(struct Renderer *song, int drum, int bank, int program);
Instrument *LoadInstrumentOrder(struct Renderer *song, int order, int drum, int bank, int program);
void SetOrder(int order, int drum, int bank, int program);
void SetAllOrders(int order);
@ -288,10 +292,10 @@ struct SFFile : public Timidity::FontFile
void AddPresetGenerators(SFGenComposite *composite, int start, int stop, SFPreset *preset);
void AddPresetGenerators(SFGenComposite *composite, int start, int stop, bool gen_set[GEN_NumGenerators]);
Timidity::Instrument *LoadPercussion(Timidity::Renderer *song, SFPerc *perc);
Timidity::Instrument *LoadPreset(Timidity::Renderer *song, SFPreset *preset);
void LoadSample(SFSample *sample);
void ApplyGeneratorsToRegion(SFGenComposite *gen, SFSample *sfsamp, Timidity::Renderer *song, Timidity::Sample *sp);
Instrument *LoadPercussion(Renderer *song, SFPerc *perc);
Instrument *LoadPreset(Renderer *song, SFPreset *preset);
void LoadSample(Renderer* song, SFSample *sample);
void ApplyGeneratorsToRegion(SFGenComposite *gen, SFSample *sfsamp, Renderer *song, Sample *sp);
SFPreset *Presets;
SFBag *PresetBags;
@ -315,4 +319,5 @@ struct SFFile : public Timidity::FontFile
int NumSamples;
};
SFFile *ReadSF2(const char *filename, FileReader &f);
SFFile *ReadSF2(const char *filename, timidity_file *f);
}

255
src/sound/timidity/t_swap.h Normal file
View file

@ -0,0 +1,255 @@
//
// DESCRIPTION:
// Endianess handling, swapping 16bit and 32bit.
//
//-----------------------------------------------------------------------------
#ifndef __M_SWAP_H__
#define __M_SWAP_H__
#include <stdlib.h>
// Endianess handling.
// WAD files are stored little endian.
#ifdef __APPLE__
#include <libkern/OSByteOrder.h>
inline short LittleShort(short x)
{
return (short)OSSwapLittleToHostInt16((uint16_t)x);
}
inline unsigned short LittleShort(unsigned short x)
{
return OSSwapLittleToHostInt16(x);
}
inline short LittleShort(int x)
{
return OSSwapLittleToHostInt16((uint16_t)x);
}
inline unsigned short LittleShort(unsigned int x)
{
return OSSwapLittleToHostInt16((uint16_t)x);
}
inline int LittleLong(int x)
{
return OSSwapLittleToHostInt32((uint32_t)x);
}
inline unsigned int LittleLong(unsigned int x)
{
return OSSwapLittleToHostInt32(x);
}
inline short BigShort(short x)
{
return (short)OSSwapBigToHostInt16((uint16_t)x);
}
inline unsigned short BigShort(unsigned short x)
{
return OSSwapBigToHostInt16(x);
}
inline int BigLong(int x)
{
return OSSwapBigToHostInt32((uint32_t)x);
}
inline unsigned int BigLong(unsigned int x)
{
return OSSwapBigToHostInt32(x);
}
#elif defined __BIG_ENDIAN__
// Swap 16bit, that is, MSB and LSB byte.
// No masking with 0xFF should be necessary.
inline short LittleShort (short x)
{
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
}
inline unsigned short LittleShort (unsigned short x)
{
return (unsigned short)((x>>8) | (x<<8));
}
inline short LittleShort (int x)
{
return LittleShort((short)x);
}
inline unsigned short LittleShort (unsigned int x)
{
return LittleShort((unsigned short)x);
}
// Swapping 32bit.
inline unsigned int LittleLong (unsigned int x)
{
return (unsigned int)(
(x>>24)
| ((x>>8) & 0xff00)
| ((x<<8) & 0xff0000)
| (x<<24));
}
inline int LittleLong (int x)
{
return (int)(
(((unsigned int)x)>>24)
| ((((unsigned int)x)>>8) & 0xff00)
| ((((unsigned int)x)<<8) & 0xff0000)
| (((unsigned int)x)<<24));
}
inline short BigShort(short x)
{
return x;
}
inline unsigned short BigShort(unsigned short x)
{
return x;
}
inline unsigned int BigLong(unsigned int x)
{
return x;
}
inline int BigLong(int x)
{
return x;
}
#else
inline short LittleShort(short x)
{
return x;
}
inline unsigned short LittleShort(unsigned short x)
{
return x;
}
inline unsigned int LittleLong(unsigned int x)
{
return x;
}
inline int LittleLong(int x)
{
return x;
}
#ifdef _MSC_VER
inline short BigShort(short x)
{
return (short)_byteswap_ushort((unsigned short)x);
}
inline unsigned short BigShort(unsigned short x)
{
return _byteswap_ushort(x);
}
inline int BigLong(int x)
{
return (int)_byteswap_ulong((unsigned long)x);
}
inline unsigned int BigLong(unsigned int x)
{
return (unsigned int)_byteswap_ulong((unsigned long)x);
}
#pragma warning (default: 4035)
#else
inline short BigShort (short x)
{
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
}
inline unsigned short BigShort (unsigned short x)
{
return (unsigned short)((x>>8) | (x<<8));
}
inline unsigned int BigLong (unsigned int x)
{
return (unsigned int)(
(x>>24)
| ((x>>8) & 0xff00)
| ((x<<8) & 0xff0000)
| (x<<24));
}
inline int BigLong (int x)
{
return (int)(
(((unsigned int)x)>>24)
| ((((unsigned int)x)>>8) & 0xff00)
| ((((unsigned int)x)<<8) & 0xff0000)
| (((unsigned int)x)<<24));
}
#endif
#endif // __BIG_ENDIAN__
// These may be destructive so they should create errors
unsigned long BigLong(unsigned long) = delete;
long BigLong(long) = delete;
unsigned long LittleLong(unsigned long) = delete;
long LittleLong(long) = delete;
// Data accessors, since some data is highly likely to be unaligned.
#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__)
inline int GetShort(const unsigned char *foo)
{
return *(const short *)foo;
}
inline int GetInt(const unsigned char *foo)
{
return *(const int *)foo;
}
#else
inline int GetShort(const unsigned char *foo)
{
return short(foo[0] | (foo[1] << 8));
}
inline int GetInt(const unsigned char *foo)
{
return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24));
}
#endif
inline int GetBigInt(const unsigned char *foo)
{
return int((foo[0] << 24) | (foo[1] << 16) | (foo[2] << 8) | foo[3]);
}
#ifdef __BIG_ENDIAN__
inline int GetNativeInt(const unsigned char *foo)
{
return GetBigInt(foo);
}
#else
inline int GetNativeInt(const unsigned char *foo)
{
return GetInt(foo);
}
#endif
#endif // __M_SWAP_H__

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);
}
static int read_config_file(const char *name, bool ismain)
class FCommandLine
{
public:
FCommandLine(const char* commandline)
{
cmd = commandline;
_argc = -1;
_argv = NULL;
argsize = 0;
}
~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;
}

View file

@ -22,151 +22,12 @@
#include <stdint.h>
#include <string>
class FileReader; // this needs to go away.
#include "timidity_file.h"
namespace Timidity
{
struct timidity_file;
/*
config.h
*/
/* Acoustic Grand Piano seems to be the usual default instrument. */
#define DEFAULT_PROGRAM 0
/* 9 here is MIDI channel 10, which is the standard percussion channel.
Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too.
On the other hand, some files know that 16 is not a drum channel and
try to play music on it. This is now a runtime option, so this isn't
a critical choice anymore. */
#define DEFAULT_DRUMCHANNELS (1<<9)
/*#define DEFAULT_DRUMCHANNELS ((1<<9) | (1<<15))*/
#define MAXCHAN 16
#define MAXNOTE 128
/* 1000 here will give a control ratio of 22:1 with 22 kHz output.
Higher CONTROLS_PER_SECOND values allow more accurate rendering
of envelopes and tremolo. The cost is CPU time. */
#define CONTROLS_PER_SECOND 1000
/* A scalar applied to the final mix to try and approximate the
volume level of FMOD's built-in MIDI player. */
#define FINAL_MIX_SCALE 0.5
/* How many bits to use for the fractional part of sample positions.
This affects tonal accuracy. The entire position counter must fit
in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of
a sample is 1048576 samples (2 megabytes in memory). The GUS gets
by with just 9 bits and a little help from its friends...
"The GUS does not SUCK!!!" -- a happy user :) */
#define FRACTION_BITS 12
/* For some reason the sample volume is always set to maximum in all
patch files. Define this for a crude adjustment that may help
equalize instrument volumes. */
//#define ADJUST_SAMPLE_VOLUMES
/* The number of samples to use for ramping out a dying note. Affects
click removal. */
#define MAX_DIE_TIME 20
/**************************************************************************/
/* Anything below this shouldn't need to be changed unless you're porting
to a new machine with other than 32-bit, big-endian words. */
/**************************************************************************/
/* change FRACTION_BITS above, not these */
#define INTEGER_BITS (32 - FRACTION_BITS)
#define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS)
#define FRACTION_MASK (~ INTEGER_MASK)
#define MAX_SAMPLE_SIZE (1 << INTEGER_BITS)
/* This is enforced by some computations that must fit in an int */
#define MAX_CONTROL_RATIO 255
#define MAX_AMPLIFICATION 800
typedef float sample_t;
typedef float final_volume_t;
#define FINAL_VOLUME(v) (v)
#define FSCALE(a,b) ((a) * (float)(1<<(b)))
#define FSCALENEG(a,b) ((a) * (1.0L / (float)(1<<(b))))
/* Vibrato and tremolo Choices of the Day */
#define SWEEP_TUNING 38
#define VIBRATO_AMPLITUDE_TUNING 1.0
#define VIBRATO_RATE_TUNING 38
#define TREMOLO_AMPLITUDE_TUNING 1.0
#define TREMOLO_RATE_TUNING 38
#define SWEEP_SHIFT 16
#define RATE_SHIFT 5
#define VIBRATO_SAMPLE_INCREMENTS 32
#ifndef PI
#define PI 3.14159265358979323846
#endif
#if defined(__GNUC__) && !defined(__clang__) && (defined(__i386__) || defined(__x86_64__))
// [RH] MinGW's pow() function is terribly slow compared to VC8's
// (I suppose because it's using an old version from MSVCRT.DLL).
// On an Opteron running x86-64 Linux, this also ended up being about
// 100 cycles faster than libm's pow(), which is why I'm using this
// for GCC in general and not just for MinGW.
// [CE] Clang doesn't yet support some inline ASM operations so I disabled it for that instance
extern __inline__ double pow_x87_inline(double x,double y)
{
double result;
if (y == 0)
{
return 1;
}
if (x == 0)
{
if (y > 0)
{
return 0;
}
else
{
union { double fp; long long ip; } infinity;
infinity.ip = 0x7FF0000000000000ll;
return infinity.fp;
}
}
__asm__ (
"fyl2x\n\t"
"fld %%st(0)\n\t"
"frndint\n\t"
"fxch\n\t"
"fsub %%st(1),%%st(0)\n\t"
"f2xm1\n\t"
"fld1\n\t"
"faddp\n\t"
"fxch\n\t"
"fld1\n\t"
"fscale\n\t"
"fstp %%st(1)\n\t"
"fmulp\n\t"
: "=t" (result)
: "0" (x), "u" (y)
: "st(1)", "st(7)" );
return result;
}
#define pow pow_x87_inline
#endif
/*
common.h
*/
extern void *safe_malloc(size_t count);
/*
controls.h
@ -187,236 +48,77 @@ enum
VERB_DEBUG
};
void cmsg(int type, int verbosity_level, const char *fmt, ...);
extern void (*cmsg)(int type, int verbosity_level, const char *fmt, ...);
/*
instrum.h
timidity.h
*/
struct DLS_Data;
struct Instrument;
struct Sample;
typedef float sample_t;
typedef float final_volume_t;
class Instruments;
enum
{
PATCH_16 = (1<<0),
PATCH_UNSIGNED = (1<<1),
PATCH_LOOPEN = (1<<2),
PATCH_BIDIR = (1<<3),
PATCH_BACKWARD = (1<<4),
PATCH_SUSTAIN = (1<<5),
PATCH_NO_SRELEASE = (1<<6),
PATCH_FAST_REL = (1<<7),
VIBRATO_SAMPLE_INCREMENTS = 32
};
struct Sample
struct MinEnvelope
{
uint8_t stage;
uint8_t bUpdating;
};
struct GF1Envelope : public MinEnvelope
{
int volume, target, increment;
int rate[6], offset[6];
void Init(struct Renderer* song, struct Voice* v);
bool Update(struct Voice* v);
bool Recompute(struct Voice* v);
void ApplyToAmp(struct Voice* v);
void Release(struct Voice* v);
};
struct SF2Envelope : public MinEnvelope
{
float volume;
float DelayTime; // timecents
float AttackTime; // timecents
float HoldTime; // timecents
float DecayTime; // timecents
float SustainLevel; // -0.1%
float ReleaseTime; // timecents
float SampleRate;
int HoldStart;
float RateMul;
float RateMul_cB;
void Init(struct Renderer* song, Voice* v);
bool Update(struct Voice* v);
void ApplyToAmp(struct Voice* v);
void Release(struct Voice* v);
};
struct Envelope
{
int32_t
loop_start, loop_end, data_length,
sample_rate;
float
low_freq, high_freq, root_freq;
union
{
struct
{
uint8_t rate[6], offset[6];
} gf1;
struct
{
short delay_vol;
short attack_vol;
short hold_vol;
short decay_vol;
short sustain_vol;
short release_vol;
} sf2;
} envelope;
sample_t *data;
int32_t
tremolo_sweep_increment, tremolo_phase_increment,
vibrato_sweep_increment, vibrato_control_ratio;
uint8_t
tremolo_depth, vibrato_depth,
low_vel, high_vel,
type;
uint16_t
modes;
int16_t
panning;
uint16_t
scale_factor, key_group;
int16_t
scale_note;
bool
self_nonexclusive;
float
left_offset, right_offset;
MinEnvelope env;
GF1Envelope gf1;
SF2Envelope sf2;
};
// SF2 stuff
int16_t tune;
int8_t velocity;
uint8_t Type;
float initial_attenuation;
};
void convert_sample_data(Sample *sample, const void *data);
void free_instruments();
/* Magic file words */
#define ID_RIFF MAKE_ID('R','I','F','F')
#define ID_LIST MAKE_ID('L','I','S','T')
#define ID_INFO MAKE_ID('I','N','F','O')
#define ID_sfbk MAKE_ID('s','f','b','k')
#define ID_sdta MAKE_ID('s','d','t','a')
#define ID_pdta MAKE_ID('p','d','t','a')
#define ID_ifil MAKE_ID('i','f','i','l')
#define ID_iver MAKE_ID('i','v','e','r')
#define ID_irom MAKE_ID('i','r','o','m')
#define ID_smpl MAKE_ID('s','m','p','l')
#define ID_sm24 MAKE_ID('s','m','2','4')
#define ID_phdr MAKE_ID('p','h','d','r')
#define ID_pbag MAKE_ID('p','b','a','g')
#define ID_pmod MAKE_ID('p','m','o','d')
#define ID_pgen MAKE_ID('p','g','e','n')
#define ID_inst MAKE_ID('i','n','s','t')
#define ID_ibag MAKE_ID('i','b','a','g')
#define ID_imod MAKE_ID('i','m','o','d')
#define ID_igen MAKE_ID('i','g','e','n')
#define ID_shdr MAKE_ID('s','h','d','r')
/* Instrument definitions */
enum
{
INST_GUS,
INST_DLS,
INST_SF2
};
struct Instrument
{
Instrument();
~Instrument();
int samples;
Sample *sample;
};
struct ToneBankElement
{
ToneBankElement() :
note(0), pan(0), strip_loop(0), strip_envelope(0), strip_tail(0)
{}
FString name;
int note, pan, fontbank, fontpreset, fontnote;
int8_t strip_loop, strip_envelope, strip_tail;
};
/* A hack to delay instrument loading until after reading the entire MIDI file. */
#define MAGIC_LOAD_INSTRUMENT ((Instrument *)(-1))
enum
{
MAXPROG = 128,
MAXBANK = 128
};
struct ToneBank
{
ToneBank();
~ToneBank();
ToneBankElement *tone;
Instrument *instrument[MAXPROG];
};
#define SPECIAL_PROGRAM -1
/*
instrum_font.cpp
*/
class FontFile
{
public:
FontFile(const char *filename);
virtual ~FontFile();
std::string Filename;
FontFile *Next;
virtual Instrument *LoadInstrument(struct Renderer *song, int drum, int bank, int program) = 0;
virtual Instrument *LoadInstrumentOrder(struct Renderer *song, int order, int drum, int bank, int program) = 0;
virtual void SetOrder(int order, int drum, int bank, int program) = 0;
virtual void SetAllOrders(int order) = 0;
};
void font_freeall();
FontFile *font_find(const char *filename);
void font_add(const char *filename, int load_order);
void font_remove(const char *filename);
void font_order(int order, int bank, int preset, int keynote);
Instrument *load_instrument_font(struct Renderer *song, const char *font, int drum, int bank, int instrument);
Instrument *load_instrument_font_order(struct Renderer *song, int order, int drum, int bank, int instrument);
FontFile *ReadDLS(const char *filename, FileReader &f);
/*
mix.h
*/
extern void mix_voice(struct Renderer *song, float *buf, struct Voice *v, int c);
extern int recompute_envelope(struct Voice *v);
extern void apply_envelope_to_amp(struct Voice *v);
/*
playmidi.h
*/
/* Midi events */
enum
{
ME_NOTEOFF = 0x80,
ME_NOTEON = 0x90,
ME_KEYPRESSURE = 0xA0,
ME_CONTROLCHANGE = 0xB0,
ME_PROGRAM = 0xC0,
ME_CHANNELPRESSURE = 0xD0,
ME_PITCHWHEEL = 0xE0
};
/* Controllers */
enum
{
CTRL_BANK_SELECT = 0,
CTRL_DATA_ENTRY = 6,
CTRL_VOLUME = 7,
CTRL_PAN = 10,
CTRL_EXPRESSION = 11,
CTRL_SUSTAIN = 64,
CTRL_HARMONICCONTENT = 71,
CTRL_RELEASETIME = 72,
CTRL_ATTACKTIME = 73,
CTRL_BRIGHTNESS = 74,
CTRL_REVERBERATION = 91,
CTRL_CHORUSDEPTH = 93,
CTRL_NRPN_LSB = 98,
CTRL_NRPN_MSB = 99,
CTRL_RPN_LSB = 100,
CTRL_RPN_MSB = 101,
CTRL_ALL_SOUNDS_OFF = 120,
CTRL_RESET_CONTROLLERS = 121,
CTRL_ALL_NOTES_OFF = 123
};
/* RPNs */
enum
{
RPN_PITCH_SENS = 0x0000,
RPN_FINE_TUNING = 0x0001,
RPN_COARSE_TUNING = 0x0002,
RPN_RESET = 0x3fff
void Init(struct Renderer* song, struct Voice* v);
bool Update(struct Voice* v);
void ApplyToAmp(struct Voice* v);
void Release(struct Voice* v);
};
struct Channel
@ -437,84 +139,11 @@ struct Channel
pitchfactor; /* precomputed pitch bend factor to save some fdiv's */
};
/* Causes the instrument's default panning to be used. */
#define NO_PANNING -1
struct MinEnvelope
{
uint8_t stage;
uint8_t bUpdating;
};
struct GF1Envelope : public MinEnvelope
{
int volume, target, increment;
int rate[6], offset[6];
void Init(struct Renderer *song, Voice *v);
bool Update(struct Voice *v);
bool Recompute(struct Voice *v);
void ApplyToAmp(struct Voice *v);
void Release(struct Voice *v);
};
struct SF2Envelope : public MinEnvelope
{
float volume;
float DelayTime; // timecents
float AttackTime; // timecents
float HoldTime; // timecents
float DecayTime; // timecents
float SustainLevel; // -0.1%
float ReleaseTime; // timecents
float SampleRate;
int HoldStart;
float RateMul;
float RateMul_cB;
void Init(struct Renderer *song, Voice *v);
bool Update(struct Voice *v);
void ApplyToAmp(struct Voice *v);
void Release(struct Voice *v);
};
struct Envelope
{
union
{
MinEnvelope env;
GF1Envelope gf1;
SF2Envelope sf2;
};
uint8_t Type;
void Init(struct Renderer *song, struct Voice *v);
bool Update(struct Voice *v)
{
if (Type == INST_GUS)
return gf1.Update(v);
return sf2.Update(v);
}
void ApplyToAmp(struct Voice *v)
{
if (Type == INST_GUS)
return gf1.ApplyToAmp(v);
return sf2.ApplyToAmp(v);
}
void Release(struct Voice *v)
{
if (Type == INST_GUS)
return gf1.Release(v);
return sf2.Release(v);
}
};
struct Voice
{
uint8_t
status, channel, note, velocity;
Sample *sample;
Sample* sample;
float
orig_frequency, frequency;
int
@ -541,78 +170,12 @@ struct Voice
sample_count;
};
/* Voice status options: */
enum
{
VOICE_RUNNING = (1<<0),
VOICE_SUSTAINING = (1<<1),
VOICE_RELEASING = (1<<2),
VOICE_STOPPING = (1<<3),
VOICE_LPE = (1<<4),
NOTE_SUSTAIN = (1<<5),
};
/* Envelope stages: */
enum
{
GF1_ATTACK,
GF1_HOLD,
GF1_DECAY,
GF1_RELEASE,
GF1_RELEASEB,
GF1_RELEASEC
};
enum
{
SF2_DELAY,
SF2_ATTACK,
SF2_HOLD,
SF2_DECAY,
SF2_SUSTAIN,
SF2_RELEASE,
SF2_FINISHED
};
#define ISDRUMCHANNEL(c) ((drumchannels & (1<<(c))))
/*
resample.h
*/
extern sample_t *resample_voice(struct Renderer *song, Voice *v, int *countptr);
extern void pre_resample(struct Renderer *song, Sample *sp);
/*
tables.h
*/
const double log_of_2 = 0.69314718055994529;
#define sine(x) (sin((2*PI/1024.0) * (x)))
#define note_to_freq(x) (float(8175.7989473096690661233836992789 * pow(2.0, (x) / 12.0)))
#define freq_to_note(x) (log((x) / 8175.7989473096690661233836992789) * (12.0 / log_of_2))
#define calc_gf1_amp(x) (pow(2.0,((x)*16.0 - 16.0))) // Actual GUS equation
/*
timidity.h
*/
struct DLS_Data;
int LoadConfig(const char *filename);
int LoadDMXGUS();
extern int LoadConfig();
extern void FreeAll();
extern ToneBank *tonebank[MAXBANK];
extern ToneBank *drumset[MAXBANK];
struct Renderer
{
//private:
float rate;
DLS_Data *patches;
Instruments* instruments;
Instrument *default_instrument;
int default_program;
int resample_buffer_size;
@ -624,8 +187,8 @@ struct Renderer
int adjust_panning_immediately;
int voices;
int lost_notes, cut_notes;
Renderer(float sample_rate, const char *args);
public:
Renderer(float sample_rate, int voices, Instruments *instr);
~Renderer();
void HandleEvent(int status, int parm1, int parm2);
@ -636,6 +199,7 @@ struct Renderer
void Reset();
int load_missing_instruments();
//private:
int set_default_instrument(const char *name);
int convert_tremolo_sweep(uint8_t sweep);
int convert_vibrato_sweep(uint8_t sweep, int vib_control_ratio);
@ -675,6 +239,15 @@ struct Renderer
void DataEntryCoarseNRPN(int chan, int nrpn, int val);
void DataEntryFineNRPN(int chan, int nrpn, int val);
int fill_bank(int dr, int b);
Instrument* load_instrument(const char* name, int percussion,
int panning, int note_to_use,
int strip_loop, int strip_envelope,
int strip_tail);
Instrument* load_instrument_font(const char* font, int drum, int bank, int instrument);
Instrument* load_instrument_font_order(int order, int drum, int bank, int instrument);
static void compute_pan(double panning, int type, float &left_offset, float &right_offset);
};

View file

@ -0,0 +1,141 @@
/*
TiMidity -- File interface for the pure sequencer.
Copyright (C) 2019 Christoph Oelckers
This program 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 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
*/
#pragma once
#include <stdio.h>
#include <vector>
#include <string>
namespace Timidity
{
struct timidity_file
{
std::string filename;
virtual ~timidity_file() {}
virtual char* gets(char* buff, int n) = 0;
virtual long read(void* buff, int32_t size, int32_t nitems) = 0;
long read(void* buff, int32_t size) { return read(buff, 1, size); }
virtual long seek(long offset, int whence) = 0;
virtual long tell() = 0;
virtual void close() = 0;
};
class SoundFontReaderInterface
{
public:
virtual ~SoundFontReaderInterface() {}
virtual struct timidity_file* open_timidity_file(const char* fn) = 0;
virtual void timidity_add_path(const char* path) = 0;
};
// A minimalistic sound font reader interface. Normally this should be replaced with something better tied into the host application.
#ifdef USE_BASE_INTERFACE
// Base version of timidity_file using stdio's FILE.
struct timidity_file_FILE : public timidity_file
{
FILE* f = nullptr;
~timidity_file_FILE()
{
if (f) fclose(f);
}
char* gets(char* buff, int n) override
{
if (!f) return nullptr;
return fgets(buff, n, f);
}
long read(void* buff, int32_t size, int32_t nitems) override
{
if (!f) return 0;
return (long)fread(buff, size, nitems, f);
}
long seek(long offset, int whence) override
{
if (!f) return 0;
return fseek(f, offset, whence);
}
long tell() override
{
if (!f) return 0;
return ftell(f);
}
void close()
{
if (f) fclose(f);
delete this;
}
};
class BaseSoundFontReader : public SoundFontReaderInterface
{
std::vector<std::string> paths;
bool IsAbsPath(const char *name)
{
if (name[0] == '/' || name[0] == '\\') return true;
#ifdef _WIN32
/* [A-Za-z]: (for Windows) */
if (isalpha(name[0]) && name[1] == ':') return true;
#endif /* _WIN32 */
return 0;
}
struct timidity_file* open_timidityplus_file(const char* fn)
{
FILE *f = nullptr;
std::string fullname;
if (!fn)
{
f = fopen("timidity.cfg", "rt");
fullname = "timidity.cfg";
}
else
{
if (!IsAbsPath(fn))
{
for(int i = (int)paths.size()-1; i>=0; i--)
{
fullname = paths[i] + fn;
f = fopen(fullname.c_str(), "rt");
break;
}
}
if (!f) f = fopen(fn, "rt");
}
if (!f) return nullptr;
auto tf = new timidity_file_FILE;
tf->f = f;
tf->filename = fullname;
return tf;
}
void timidityplus_add_path(const char* path)
{
std::string p = path;
if (p.back() != '/' && p.back() != '\\') p += '/'; // always let it end with a slash.
paths.push_back(p);
}
};
#endif
}