From d557f609cfe7aabc8e912ccac0d6ca7549a751a0 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 24 Sep 2019 23:08:56 +0200 Subject: [PATCH] - cleanup of the TimidityMIDIDevice(GUS) backend code to eliminate the storage in global variables and to remove the dependencies on core ZDoom code. The organization here is now the same as for the Timidity++ device, i.e. it is the device owning the instruments to give better control over their lifecycle. --- libraries/timidityplus/common.cpp | 3 +- .../timidityplus/timiditypp/timidity_file.h | 6 + .../mididevices/music_timidity_mididevice.cpp | 216 ++++--- src/sound/music/i_music.cpp | 2 +- src/sound/music/i_musicinterns.h | 1 + src/sound/music/i_soundfont.cpp | 31 +- src/sound/music/i_soundfont.h | 20 +- src/sound/timidity/common.cpp | 1 - src/sound/timidity/common.h | 142 +++++ src/sound/timidity/instrum.cpp | 144 +++-- src/sound/timidity/instrum.h | 193 +++++++ src/sound/timidity/instrum.obj | Bin 20356 -> 0 bytes src/sound/timidity/instrum_dls.cpp | 6 +- src/sound/timidity/instrum_font.cpp | 49 +- src/sound/timidity/instrum_sf2.cpp | 126 ++-- src/sound/timidity/mix.cpp | 4 + src/sound/timidity/playmidi.cpp | 31 +- src/sound/timidity/playmidi.h | 122 ++++ src/sound/timidity/resample.cpp | 8 +- src/sound/timidity/sf2.h | 21 +- src/sound/timidity/t_swap.h | 255 +++++++++ src/sound/timidity/timidity.cpp | 412 ++++++------- src/sound/timidity/timidity.h | 541 ++---------------- src/sound/timidity/timidity_file.h | 141 +++++ 24 files changed, 1553 insertions(+), 922 deletions(-) create mode 100644 src/sound/timidity/common.h create mode 100644 src/sound/timidity/instrum.h delete mode 100644 src/sound/timidity/instrum.obj create mode 100644 src/sound/timidity/playmidi.h create mode 100644 src/sound/timidity/t_swap.h create mode 100644 src/sound/timidity/timidity_file.h 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 5ea265d40a19a739823c4b43c31c267d835cfe83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20356 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 qo|FX*7fO8yfx$Y3wk+PzB;KNy_Lg`{Lu #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); } - -static int read_config_file(const char *name, bool ismain) +class FCommandLine +{ +public: + FCommandLine(const char* commandline) + { + cmd = commandline; + _argc = -1; + _argv = NULL; + argsize = 0; + } + + ~FCommandLine() + { + if (_argv != NULL) + { + delete[] _argv; + } + } + + int argc() + { + if (_argc == -1) + { + argsize = ParseCommandLine(cmd, &_argc, NULL); + } + return _argc; + } + + char* operator[] (int i) + { + if (_argv == NULL) + { + int count = argc(); + _argv = new char* [count + (argsize + sizeof(char*) - 1) / sizeof(char*)]; + _argv[0] = (char*)_argv + count * sizeof(char*); + ParseCommandLine(cmd, NULL, _argv); + } + return _argv[i]; + } + + + const char* args() { return cmd; } + + void Shift() + { + // Only valid after _argv has been filled. + for (int i = 1; i < _argc; ++i) + { + _argv[i - 1] = _argv[i]; + } + } + +private: + const char* cmd; + int _argc; + char** _argv; + long argsize; +}; + + +int Instruments::read_config_file(const char *name) { - FileReader fp; char tmp[1024], *cp; ToneBank *bank = NULL; int i, j, k, line = 0, words; @@ -92,28 +196,20 @@ static int read_config_file(const char *name, bool ismain) if (rcf_count > 50) { - Printf("Timidity: Probable source loop in configuration files\n"); + cmsg(CMSG_ERROR, VERB_NORMAL, "Timidity: Probable source loop in configuration files\n"); return (-1); } - if (ismain) - { - if (!InitReader(name)) return -1; - fp = gus_sfreader->OpenMainConfigFile(); - FreeAll(); - } - else - { - fp = gus_sfreader->LookupFile(name).first; - } - if (!fp .isOpen()) + auto fp = sfreader->open_timidity_file(name); + if (!fp) { + cmsg(CMSG_ERROR, VERB_NORMAL, "Timidity: Unable to open config file\n"); return -1; } - while (fp.Gets(tmp, sizeof(tmp))) + while (fp->gets(tmp, sizeof(tmp))) { line++; - FCommandLine w(tmp, true); + FCommandLine w(tmp); words = w.argc(); if (words == 0) continue; @@ -171,7 +267,7 @@ static int read_config_file(const char *name, bool ismain) * before TiMidity kills the note. This may be useful to implement * later, but I don't see any urgent need for it. */ - //Printf("FIXME: Implement \"timeout\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"timeout\" in TiMidity config.\n"); } else if (!strcmp(w[0], "copydrumset") /* "copydrumset" drumset */ || !strcmp(w[0], "copybank")) /* "copybank" bank */ @@ -181,7 +277,7 @@ static int read_config_file(const char *name, bool ismain) * the current drumset or bank. May be useful later, but not a * high priority. */ - //Printf("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"%s\" in TiMidity config.\n", w[0]); } else if (!strcmp(w[0], "undef")) /* "undef" progno */ { @@ -189,7 +285,7 @@ static int read_config_file(const char *name, bool ismain) * Undefines the tone "progno" of the current tone bank (or * drum set?). Not a high priority. */ - //Printf("FIXME: Implement \"undef\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"undef\" in TiMidity config.\n"); } else if (!strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */ { @@ -197,7 +293,7 @@ static int read_config_file(const char *name, bool ismain) * Sets the alternate assign for drum set. Whatever that's * supposed to mean. */ - //Printf("FIXME: Implement \"altassign\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"altassign\" in TiMidity config.\n"); } else if (!strcmp(w[0], "soundfont")) { @@ -208,7 +304,7 @@ static int read_config_file(const char *name, bool ismain) */ if (words < 2) { - Printf("%s: line %d: No soundfont given\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No soundfont given\n", name, line); return -2; } if (words > 2 && !strcmp(w[2], "remove")) @@ -223,7 +319,7 @@ static int read_config_file(const char *name, bool ismain) { if (!(cp = strchr(w[i], '='))) { - Printf("%s: line %d: bad soundfont option %s\n", name, line, w[i]); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad soundfont option %s\n", name, line, w[i]); return -2; } } @@ -240,7 +336,7 @@ static int read_config_file(const char *name, bool ismain) if (words < 3) { - Printf("%s: line %d: syntax error\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: syntax error\n", name, line); return -2; } @@ -256,7 +352,7 @@ static int read_config_file(const char *name, bool ismain) } else { - Printf("%s: line %d: font subcommand must be 'order' or 'exclude'\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: font subcommand must be 'order' or 'exclude'\n", name, line); return -2; } if (i < words) @@ -290,7 +386,7 @@ static int read_config_file(const char *name, bool ismain) * apparently it sets some sort of base offset for tone numbers. * Why anyone would want to do this is beyond me. */ - //Printf("FIXME: Implement \"progbase\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"progbase\" in TiMidity config.\n"); } else if (!strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */ { @@ -300,7 +396,7 @@ static int read_config_file(const char *name, bool ismain) * documentation whatsoever for it, but it looks like it's used * for remapping one instrument to another somehow. */ - //Printf("FIXME: Implement \"map\" in TiMidity config.\n"); + //cmsg(CMSG_ERROR, VERB_NORMAL, "FIXME: Implement \"map\" in TiMidity config.\n"); } /* Standard TiMidity config */ @@ -309,26 +405,26 @@ static int read_config_file(const char *name, bool ismain) { if (words < 2) { - Printf("%s: line %d: No directory given\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No directory given\n", name, line); return -2; } for (i = 1; i < words; i++) { // Q: How does this deal with relative paths? In this form it just does not work. - gus_sfreader->AddPath(w[i]); + sfreader->timidity_add_path(w[i]); } } else if (!strcmp(w[0], "source")) { if (words < 2) { - Printf("%s: line %d: No file name given\n", name, line); + cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: No file name given\n", name, line); return -2; } for (i=1; i 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 + +}