diff --git a/libraries/timidityplus/common.cpp b/libraries/timidityplus/common.cpp index f4908b93a..78b530016 100644 --- a/libraries/timidityplus/common.cpp +++ b/libraries/timidityplus/common.cpp @@ -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); diff --git a/libraries/timidityplus/timiditypp/timidity_file.h b/libraries/timidityplus/timiditypp/timidity_file.h index 2798f2763..df1a73c0b 100644 --- a/libraries/timidityplus/timiditypp/timidity_file.h +++ b/libraries/timidityplus/timiditypp/timidity_file.h @@ -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 diff --git a/src/sound/mididevices/music_timidity_mididevice.cpp b/src/sound/mididevices/music_timidity_mididevice.cpp index b9ce33d6f..fbc87d137 100644 --- a/src/sound/mididevices/music_timidity_mididevice.cpp +++ b/src/sound/mididevices/music_timidity_mididevice.cpp @@ -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 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; +} diff --git a/src/sound/music/i_music.cpp b/src/sound/music/i_music.cpp index 320495b75..dd763e679 100644 --- a/src/sound/music/i_music.cpp +++ b/src/sound/music/i_music.cpp @@ -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(); diff --git a/src/sound/music/i_musicinterns.h b/src/sound/music/i_musicinterns.h index 09bba62dc..ce68f4b9d 100644 --- a/src/sound/music/i_musicinterns.h +++ b/src/sound/music/i_musicinterns.h @@ -88,6 +88,7 @@ public: +void Timidity_Shutdown(); void TimidityPP_Shutdown(); // Base class for software synthesizer MIDI output devices ------------------ diff --git a/src/sound/music/i_soundfont.cpp b/src/sound/music/i_soundfont.cpp index 70dc8ffe0..d7d0d9c7c 100644 --- a/src/sound/music/i_soundfont.cpp +++ b/src/sound/music/i_soundfont.cpp @@ -44,7 +44,8 @@ FSoundFontManager sfmanager; -struct timidity_file_FileReader : public TimidityPlus::timidity_file +template +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; 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; + 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; diff --git a/src/sound/music/i_soundfont.h b/src/sound/music/i_soundfont.h index f100ff94f..e29c1147a 100644 --- a/src/sound/music/i_soundfont.h +++ b/src/sound/music/i_soundfont.h @@ -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; diff --git a/src/sound/timidity/common.cpp b/src/sound/timidity/common.cpp index 09908f851..64687842a 100644 --- a/src/sound/timidity/common.cpp +++ b/src/sound/timidity/common.cpp @@ -24,7 +24,6 @@ #include #include #include -#include "timidity.h" namespace Timidity diff --git a/src/sound/timidity/common.h b/src/sound/timidity/common.h new file mode 100644 index 000000000..9554a0e58 --- /dev/null +++ b/src/sound/timidity/common.h @@ -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); + + +} diff --git a/src/sound/timidity/instrum.cpp b/src/sound/timidity/instrum.cpp index a7f03ff4a..90db5ae3c 100644 --- a/src/sound/timidity/instrum.cpp +++ b/src/sound/timidity/instrum.cpp @@ -30,17 +30,18 @@ #include #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 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; } + } diff --git a/src/sound/timidity/instrum.h b/src/sound/timidity/instrum.h new file mode 100644 index 000000000..6f0679686 --- /dev/null +++ b/src/sound/timidity/instrum.h @@ -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); + +} diff --git a/src/sound/timidity/instrum.obj b/src/sound/timidity/instrum.obj deleted file mode 100644 index 5ea265d40..000000000 Binary files a/src/sound/timidity/instrum.obj and /dev/null differ diff --git a/src/sound/timidity/instrum_dls.cpp b/src/sound/timidity/instrum_dls.cpp index b246fdc60..566e3161b 100644 --- a/src/sound/timidity/instrum_dls.cpp +++ b/src/sound/timidity/instrum_dls.cpp @@ -25,8 +25,12 @@ #include #include -#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__ diff --git a/src/sound/timidity/instrum_font.cpp b/src/sound/timidity/instrum_font.cpp index 5d9f2efa0..5eda23147 100644 --- a/src/sound/timidity/instrum_font.cpp +++ b/src/sound/timidity/instrum_font.cpp @@ -3,21 +3,22 @@ #include #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 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; - } - } } } diff --git a/src/sound/timidity/instrum_sf2.cpp b/src/sound/timidity/instrum_sf2.cpp index 24b757875..be7717d40 100644 --- a/src/sound/timidity/instrum_sf2.cpp +++ b/src/sound/timidity/instrum_sf2.cpp @@ -5,18 +5,17 @@ #include #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 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(); } +} \ No newline at end of file diff --git a/src/sound/timidity/mix.cpp b/src/sound/timidity/mix.cpp index b2d23839c..a2cc249c1 100644 --- a/src/sound/timidity/mix.cpp +++ b/src/sound/timidity/mix.cpp @@ -26,6 +26,10 @@ #include #include "timidity.h" +#include "common.h" +#include "instrum.h" +#include "playmidi.h" + namespace Timidity { diff --git a/src/sound/timidity/playmidi.cpp b/src/sound/timidity/playmidi.cpp index 835f4e306..83d327666 100644 --- a/src/sound/timidity/playmidi.cpp +++ b/src/sound/timidity/playmidi.cpp @@ -27,10 +27,33 @@ #include #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); diff --git a/src/sound/timidity/playmidi.h b/src/sound/timidity/playmidi.h new file mode 100644 index 000000000..3cb605e12 --- /dev/null +++ b/src/sound/timidity/playmidi.h @@ -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 +} diff --git a/src/sound/timidity/resample.cpp b/src/sound/timidity/resample.cpp index 254a7d060..85f342cf4 100644 --- a/src/sound/timidity/resample.cpp +++ b/src/sound/timidity/resample.cpp @@ -26,6 +26,10 @@ #include #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; diff --git a/src/sound/timidity/sf2.h b/src/sound/timidity/sf2.h index 89d48bd9e..50c093857 100644 --- a/src/sound/timidity/sf2.h +++ b/src/sound/timidity/sf2.h @@ -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); +} \ No newline at end of file diff --git a/src/sound/timidity/t_swap.h b/src/sound/timidity/t_swap.h new file mode 100644 index 000000000..de9b7780a --- /dev/null +++ b/src/sound/timidity/t_swap.h @@ -0,0 +1,255 @@ +// +// DESCRIPTION: +// Endianess handling, swapping 16bit and 32bit. +// +//----------------------------------------------------------------------------- + + +#ifndef __M_SWAP_H__ +#define __M_SWAP_H__ + +#include + +// Endianess handling. +// WAD files are stored little endian. + +#ifdef __APPLE__ +#include + +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__ diff --git a/src/sound/timidity/timidity.cpp b/src/sound/timidity/timidity.cpp index b6fbd1606..4506c5b16 100644 --- a/src/sound/timidity/timidity.cpp +++ b/src/sound/timidity/timidity.cpp @@ -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 gus_sfreader; -static bool InitReader(const char *config_file) +static long ParseCommandLine(const char* args, int* argc, char** argv) { - auto reader = sfmanager.OpenSoundFont(config_file, SF_GUS|SF_SF2); - if (reader == nullptr) + int count; + char* buffplace; + + count = 0; + buffplace = NULL; + if (argv != NULL) { - Printf(TEXTCOLOR_RED "%s: Unable to load sound font\n", config_file); - return false; // No sound font could be opened. + buffplace = argv[0]; } - gus_sfreader.reset(reader); - //config_name = config_file; - return true; + + for (;;) + { + while (*args <= ' ' && *args) + { // skip white space + args++; + } + if (*args == 0) + { + break; + } + else if (*args == '\"') + { // read quoted string + char stuff; + if (argv != NULL) + { + argv[count] = buffplace; + } + count++; + args++; + do + { + stuff = *args++; + if (stuff == '\"') + { + stuff = 0; + } + else if (stuff == 0) + { + args--; + } + if (argv != NULL) + { + *buffplace = stuff; + } + buffplace++; + } while (stuff); + } + else + { // read unquoted string + const char* start = args++, * end; + + while (*args && *args > ' ' && *args != '\"') + args++; + + end = args; + if (argv != NULL) + { + argv[count] = buffplace; + while (start < end) + * buffplace++ = *start++; + *buffplace++ = 0; + } + else + { + buffplace += end - start + 1; + } + count++; + } + } + if (argc != NULL) + { + *argc = count; + } + return (long)(buffplace - (char*)0); } +class FCommandLine +{ +public: + FCommandLine(const char* commandline) + { + cmd = commandline; + _argc = -1; + _argv = NULL; + argsize = 0; + } -static int read_config_file(const char *name, bool ismain) + ~FCommandLine() + { + if (_argv != NULL) + { + delete[] _argv; + } + } + + int argc() + { + if (_argc == -1) + { + argsize = ParseCommandLine(cmd, &_argc, NULL); + } + return _argc; + } + + char* operator[] (int i) + { + if (_argv == NULL) + { + int count = argc(); + _argv = new char* [count + (argsize + sizeof(char*) - 1) / sizeof(char*)]; + _argv[0] = (char*)_argv + count * sizeof(char*); + ParseCommandLine(cmd, NULL, _argv); + } + return _argv[i]; + } + + + const char* args() { return cmd; } + + void Shift() + { + // Only valid after _argv has been filled. + for (int i = 1; i < _argc; ++i) + { + _argv[i - 1] = _argv[i]; + } + } + +private: + const char* cmd; + int _argc; + char** _argv; + long argsize; +}; + + +int Instruments::read_config_file(const char *name) { - FileReader fp; char tmp[1024], *cp; ToneBank *bank = NULL; int i, j, k, line = 0, words; @@ -92,28 +196,20 @@ static int read_config_file(const char *name, bool ismain) if (rcf_count > 50) { - Printf("Timidity: Probable source loop in configuration files\n"); + cmsg(CMSG_ERROR, VERB_NORMAL, "Timidity: Probable source loop in configuration files\n"); return (-1); } - if (ismain) - { - if (!InitReader(name)) return -1; - fp = gus_sfreader->OpenMainConfigFile(); - FreeAll(); - } - else - { - fp = gus_sfreader->LookupFile(name).first; - } - if (!fp .isOpen()) + auto fp = sfreader->open_timidity_file(name); + if (!fp) { + cmsg(CMSG_ERROR, VERB_NORMAL, "Timidity: Unable to open config file\n"); return -1; } - while (fp.Gets(tmp, sizeof(tmp))) + while (fp->gets(tmp, sizeof(tmp))) { line++; - FCommandLine w(tmp, true); + FCommandLine w(tmp); words = w.argc(); if (words == 0) continue; @@ -171,7 +267,7 @@ static int read_config_file(const char *name, bool ismain) * before TiMidity kills the note. This may be useful to implement * later, but I don't see any urgent need for it. */ - //Printf("FIXME: Implement \"timeout\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"timeout\" in TiMidity config.\n"); } else if (!strcmp(w[0], "copydrumset") /* "copydrumset" drumset */ || !strcmp(w[0], "copybank")) /* "copybank" bank */ @@ -181,7 +277,7 @@ static int read_config_file(const char *name, bool ismain) * the current drumset or bank. May be useful later, but not a * high priority. */ - //Printf("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"%s\" in TiMidity config.\n", w[0]); } else if (!strcmp(w[0], "undef")) /* "undef" progno */ { @@ -189,7 +285,7 @@ static int read_config_file(const char *name, bool ismain) * Undefines the tone "progno" of the current tone bank (or * drum set?). Not a high priority. */ - //Printf("FIXME: Implement \"undef\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"undef\" in TiMidity config.\n"); } else if (!strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */ { @@ -197,7 +293,7 @@ static int read_config_file(const char *name, bool ismain) * Sets the alternate assign for drum set. Whatever that's * supposed to mean. */ - //Printf("FIXME: Implement \"altassign\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"altassign\" in TiMidity config.\n"); } else if (!strcmp(w[0], "soundfont")) { @@ -208,7 +304,7 @@ static int read_config_file(const char *name, bool ismain) */ if (words < 2) { - Printf("%s: line %d: No soundfont given\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No soundfont given\n", name, line); return -2; } if (words > 2 && !strcmp(w[2], "remove")) @@ -223,7 +319,7 @@ static int read_config_file(const char *name, bool ismain) { if (!(cp = strchr(w[i], '='))) { - Printf("%s: line %d: bad soundfont option %s\n", name, line, w[i]); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad soundfont option %s\n", name, line, w[i]); return -2; } } @@ -240,7 +336,7 @@ static int read_config_file(const char *name, bool ismain) if (words < 3) { - Printf("%s: line %d: syntax error\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error\n", name, line); return -2; } @@ -256,7 +352,7 @@ static int read_config_file(const char *name, bool ismain) } else { - Printf("%s: line %d: font subcommand must be 'order' or 'exclude'\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: font subcommand must be 'order' or 'exclude'\n", name, line); return -2; } if (i < words) @@ -290,7 +386,7 @@ static int read_config_file(const char *name, bool ismain) * apparently it sets some sort of base offset for tone numbers. * Why anyone would want to do this is beyond me. */ - //Printf("FIXME: Implement \"progbase\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"progbase\" in TiMidity config.\n"); } else if (!strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */ { @@ -300,7 +396,7 @@ static int read_config_file(const char *name, bool ismain) * documentation whatsoever for it, but it looks like it's used * for remapping one instrument to another somehow. */ - //Printf("FIXME: Implement \"map\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"map\" in TiMidity config.\n"); } /* Standard TiMidity config */ @@ -309,26 +405,26 @@ static int read_config_file(const char *name, bool ismain) { if (words < 2) { - Printf("%s: line %d: No directory given\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No directory given\n", name, line); return -2; } for (i = 1; i < words; i++) { // Q: How does this deal with relative paths? In this form it just does not work. - gus_sfreader->AddPath(w[i]); + sfreader->timidity_add_path(w[i]); } } else if (!strcmp(w[0], "source")) { if (words < 2) { - Printf("%s: line %d: No file name given\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No file name given\n", name, line); return -2; } for (i=1; i 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; + + } diff --git a/src/sound/timidity/timidity.h b/src/sound/timidity/timidity.h index d64950e56..35ba53bfe 100644 --- a/src/sound/timidity/timidity.h +++ b/src/sound/timidity/timidity.h @@ -22,151 +22,12 @@ #include #include - -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,259 +48,24 @@ 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 -{ - 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; -}; - -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 -}; - -struct Channel -{ - int - bank, program, sustain, pitchbend, - mono, /* one note only on this channel */ - pitchsens; - uint8_t - volume, expression; - int8_t - panning; - uint16_t - rpn, nrpn; - bool - nrpn_mode; - float - 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; @@ -451,11 +77,11 @@ 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); + 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 @@ -472,10 +98,10 @@ struct SF2Envelope : public MinEnvelope 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); + void Init(struct Renderer* song, Voice* v); + bool Update(struct Voice* v); + void ApplyToAmp(struct Voice* v); + void Release(struct Voice* v); }; struct Envelope @@ -489,32 +115,35 @@ struct Envelope 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); - } + 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 +{ + int + bank, program, sustain, pitchbend, + mono, /* one note only on this channel */ + pitchsens; + uint8_t + volume, expression; + int8_t + panning; + uint16_t + rpn, nrpn; + bool + nrpn_mode; + float + pitchfactor; /* precomputed pitch bend factor to save some fdiv's */ }; 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); }; diff --git a/src/sound/timidity/timidity_file.h b/src/sound/timidity/timidity_file.h new file mode 100644 index 000000000..67eeb17d8 --- /dev/null +++ b/src/sound/timidity/timidity_file.h @@ -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 +#include +#include + +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 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 + +}